Skip to content

Commit

Permalink
interp: fix sending object implementing an interface through channel
Browse files Browse the repository at this point in the history
A channel can be used to interchange data with the pre-compiled
runtime and therefore objects impletementing interfaces must be
wrapped if necessary, using genInterfaceWrapper.

A similar treatment could be applied when sending interpreted
functions over a channel, to be provided in a new PR.

Fixes #1010.
  • Loading branch information
mvertes authored Jan 26, 2021
1 parent bd60de5 commit 100d090
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 24 deletions.
22 changes: 22 additions & 0 deletions _test/issue-1010.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"encoding/json"
"fmt"
)

type MyJsonMarshaler struct{ n int }

func (m MyJsonMarshaler) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`{"num": %d}`, m.n)), nil
}

func main() {
ch := make(chan json.Marshaler, 1)
ch <- MyJsonMarshaler{2}
m, err := json.Marshal(<-ch)
fmt.Println(string(m), err)
}

// Output:
// {"num":2} <nil>
57 changes: 33 additions & 24 deletions interp/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2392,7 +2392,7 @@ func doComposite(n *node, hasType bool, keyed bool) {
l := n.level
n.exec = func(f *frame) bltn {
typ.mu.Lock()
// No need to call zero() as doComposite is only called for a structT
// No need to call zero() as doComposite is only called for a structT.
a := reflect.New(typ.TypeOf()).Elem()
typ.mu.Unlock()
for i, v := range values {
Expand Down Expand Up @@ -3148,35 +3148,44 @@ func convertConstantValue(n *node) {
// Write to a channel.
func send(n *node) {
next := getExec(n.tnext)
value0 := genValue(n.child[0]) // channel
convertLiteralValue(n.child[1], n.child[0].typ.val.TypeOf())
value1 := genValue(n.child[1]) // value to send
c0, c1 := n.child[0], n.child[1]
value0 := genValue(c0) // Send channel.
convertLiteralValue(c1, c0.typ.val.TypeOf())

if n.interp.cancelChan {
// Cancellable send
n.exec = func(f *frame) bltn {
ch, data := value0(f), value1(f)
// Fast: send on channel doesn't block
if ok := ch.TrySend(data); ok {
return next
}
// Slow: send on channel blocks, allow cancel
f.mutex.RLock()
done := f.done
f.mutex.RUnlock()
var value1 func(*frame) reflect.Value // Value to send.
switch {
case isInterfaceBin(c0.typ.val):
value1 = genInterfaceWrapper(c1, c0.typ.val.rtype)
default:
value1 = genValue(c1)
}

chosen, _, _ := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectSend, Chan: ch, Send: data}})
if chosen == 0 {
return nil
}
return next
}
} else {
// Blocking send (less overhead)
if !n.interp.cancelChan {
// Send is non-cancellable, has the least overhead.
n.exec = func(f *frame) bltn {
value0(f).Send(value1(f))
return next
}
return
}

// Send is cancellable, may have some overhead.
n.exec = func(f *frame) bltn {
ch, data := value0(f), value1(f)
// Fast: send on channel doesn't block.
if ok := ch.TrySend(data); ok {
return next
}
// Slow: send on channel blocks, allow cancel.
f.mutex.RLock()
done := f.done
f.mutex.RUnlock()

chosen, _, _ := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectSend, Chan: ch, Send: data}})
if chosen == 0 {
return nil
}
return next
}
}

Expand Down
4 changes: 4 additions & 0 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,10 @@ func isInterfaceSrc(t *itype) bool {
return t.cat == interfaceT || (t.cat == aliasT && isInterfaceSrc(t.val))
}

func isInterfaceBin(t *itype) bool {
return t.cat == valueT && t.rtype.Kind() == reflect.Interface
}

func isInterface(t *itype) bool {
return isInterfaceSrc(t) || t.TypeOf() != nil && t.TypeOf().Kind() == reflect.Interface
}
Expand Down

0 comments on commit 100d090

Please sign in to comment.