Commit e39e6682 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent dea433be
// Copyright (C) 2018 Nexedi SA and Contributors. // Copyright (C) 2018-2020 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com> // Kirill Smelkov <kirr@nexedi.com>
// //
// based on: // based on:
// https://groups.google.com/d/msg/golang-nuts/PYWxjT2v6ps/dL71oJk1mXEJ // https://groups.google.com/d/msg/golang-nuts/PYWxjT2v6ps/dL71oJk1mXEJ
...@@ -24,15 +24,20 @@ ...@@ -24,15 +24,20 @@
// Package weak provides weak references for Go. // Package weak provides weak references for Go.
package weak package weak
//go:generate gotrace gen .
import ( import (
"fmt"
"runtime" "runtime"
"sync" "sync"
// "time"
"unsafe" "unsafe"
) )
// iface is how Go runtime represents an interface. // iface is how Go runtime represents an interface.
// //
// NOTE layout must be synchronized to Go runtime representation. // NOTE layout must be synchronized to Go runtime representation.
// NOTE correctness depends on non-moving property of Go GC.
type iface struct { type iface struct {
typ uintptr // type typ uintptr // type
data uintptr // data data uintptr // data
...@@ -63,6 +68,8 @@ type Ref struct { ...@@ -63,6 +68,8 @@ type Ref struct {
state weakRefState state weakRefState
} }
//trace:event traceRelease(w *Ref, released bool)
// NewRef creates new weak reference pointing to obj. // NewRef creates new weak reference pointing to obj.
// //
// TODO + onrelease callback? // TODO + onrelease callback?
...@@ -77,6 +84,12 @@ func NewRef(obj interface{}) *Ref { ...@@ -77,6 +84,12 @@ func NewRef(obj interface{}) *Ref {
var release func(interface{}) var release func(interface{})
release = func(obj interface{}) { release = func(obj interface{}) {
// assert that the object was not moved
iobj := *(*iface)(unsafe.Pointer(&obj))
if w.iface != iobj {
panic(fmt.Sprintf("weak: release: object moved: w.iface=%x obj=%x", w.iface, iobj))
}
// GC decided that the object is no longer reachable and // GC decided that the object is no longer reachable and
// scheduled us to run as finalizer. During the time till we // scheduled us to run as finalizer. During the time till we
// actually run, Ref.Get might have been come to run and // actually run, Ref.Get might have been come to run and
...@@ -87,16 +100,21 @@ func NewRef(obj interface{}) *Ref { ...@@ -87,16 +100,21 @@ func NewRef(obj interface{}) *Ref {
if w.state == objGot { if w.state == objGot {
w.state = objLive w.state = objLive
runtime.SetFinalizer(obj, release) runtime.SetFinalizer(obj, release)
traceRelease(w, false)
} else { } else {
w.state = objReleased w.state = objReleased
traceRelease(w, true)
} }
w.mu.Unlock() w.mu.Unlock()
} }
runtime.SetFinalizer(obj, release) runtime.SetFinalizer(obj, release)
return w return w
} }
//trace:event traceGotPre(w *Ref)
// Get returns object pointed to by this weak reference. // Get returns object pointed to by this weak reference.
// //
// If original object is still alive - it is returned. // If original object is still alive - it is returned.
...@@ -106,6 +124,12 @@ func (w *Ref) Get() (obj interface{}) { ...@@ -106,6 +124,12 @@ func (w *Ref) Get() (obj interface{}) {
if w.state != objReleased { if w.state != objReleased {
w.state = objGot w.state = objGot
traceGotPre(w)
//time.Sleep(100*time.Nanosecond)
//time.Sleep(10*time.Millisecond)
//runtime.GC()
//runtime.GC()
// recreate interface{} from saved words. // recreate interface{} from saved words.
// XXX do writes as pointers so that compiler emits write barriers to notify GC? // XXX do writes as pointers so that compiler emits write barriers to notify GC?
i := (*iface)(unsafe.Pointer(&obj)) i := (*iface)(unsafe.Pointer(&obj))
......
...@@ -24,6 +24,8 @@ import ( ...@@ -24,6 +24,8 @@ import (
"testing" "testing"
"time" "time"
"unsafe" "unsafe"
"lab.nexedi.com/kirr/go123/tracing"
) )
// verify that interface <-> iface works ok. // verify that interface <-> iface works ok.
...@@ -60,6 +62,22 @@ func TestWeakRef(t *testing.T) { ...@@ -60,6 +62,22 @@ func TestWeakRef(t *testing.T) {
w := NewRef(p) w := NewRef(p)
pptr := uintptr(unsafe.Pointer(p)) pptr := uintptr(unsafe.Pointer(p))
wrelease := make(chan bool) // events from traceRelease(w)
tpg := &tracing.ProbeGroup{}
tracing.Lock()
traceRelease_Attach(tpg, func(w_ *Ref, released bool) {
if w_ != w {
panic("release: w != w_")
}
wrelease <- released
})
traceGotPre_Attach(tpg, func(w *Ref) {
// nop for now
//panic("TODO GotPre")
})
tracing.Unlock()
defer tpg.Done()
assertEq := func(a, b interface{}) { assertEq := func(a, b interface{}) {
t.Helper() t.Helper()
if a != b { if a != b {
...@@ -68,24 +86,39 @@ func TestWeakRef(t *testing.T) { ...@@ -68,24 +86,39 @@ func TestWeakRef(t *testing.T) {
} }
// perform GC + give finalizers a chance to run. // perform GC + give finalizers a chance to run.
GC := func() { GC := func(expectRelease bool) {
t.Helper()
runtime.GC() runtime.GC()
// GC only queues finalizers, not runs them directly. Give it // GC only queues finalizers, not runs them directly. Give it
// some time so that finalizers could have been run. // some time so that finalizers could have been run.
time.Sleep(10 * time.Millisecond) // XXX hack if expectRelease {
select {
case <-wrelease:
// ok
case <-time.After(100 * time.Millisecond):
t.Fatal("no release event")
}
} else {
select {
case <-time.After(10 * time.Millisecond):
// ok
case <-wrelease:
t.Fatal("unexpected release event")
}
}
} }
assertEq(w.state, objLive) assertEq(w.state, objLive)
assertEq(w.Get(), p) assertEq(w.Get(), p)
assertEq(w.state, objGot) assertEq(w.state, objGot)
GC() GC(false)
assertEq(w.state, objGot) // fin has not been run at all (p is live) assertEq(w.state, objGot) // fin has not been run at all (p is live)
assertEq(w.Get(), p) assertEq(w.Get(), p)
assertEq(w.state, objGot) assertEq(w.state, objGot)
p = nil p = nil
GC() GC(true)
assertEq(w.state, objLive) // fin ran and downgraded got -> live assertEq(w.state, objLive) // fin ran and downgraded got -> live
switch p_ := w.Get().(type) { switch p_ := w.Get().(type) {
default: default:
...@@ -97,10 +130,10 @@ func TestWeakRef(t *testing.T) { ...@@ -97,10 +130,10 @@ func TestWeakRef(t *testing.T) {
} }
assertEq(w.state, objGot) assertEq(w.state, objGot)
GC() GC(true)
assertEq(w.state, objLive) // fin ran again and again downgraded got -> live assertEq(w.state, objLive) // fin ran again and again downgraded got -> live
GC() GC(true)
assertEq(w.state, objReleased) // fin ran again and released the object assertEq(w.state, objReleased) // fin ran again and released the object
assertEq(w.Get(), nil) assertEq(w.Get(), nil)
} }
// Code generated by lab.nexedi.com/kirr/go123/tracing/cmd/gotrace; DO NOT EDIT.
package weak
// code generated for tracepoints
import (
"lab.nexedi.com/kirr/go123/tracing"
"unsafe"
)
// traceevent: traceGotPre(w *Ref)
type _t_traceGotPre struct {
tracing.Probe
probefunc func(w *Ref)
}
var _traceGotPre *_t_traceGotPre
func traceGotPre(w *Ref) {
if _traceGotPre != nil {
_traceGotPre_run(w)
}
}
func _traceGotPre_run(w *Ref) {
for p := _traceGotPre; p != nil; p = (*_t_traceGotPre)(unsafe.Pointer(p.Next())) {
p.probefunc(w)
}
}
func traceGotPre_Attach(pg *tracing.ProbeGroup, probe func(w *Ref)) *tracing.Probe {
p := _t_traceGotPre{probefunc: probe}
tracing.AttachProbe(pg, (**tracing.Probe)(unsafe.Pointer(&_traceGotPre)), &p.Probe)
return &p.Probe
}
// traceevent: traceRelease(w *Ref, released bool)
type _t_traceRelease struct {
tracing.Probe
probefunc func(w *Ref, released bool)
}
var _traceRelease *_t_traceRelease
func traceRelease(w *Ref, released bool) {
if _traceRelease != nil {
_traceRelease_run(w, released)
}
}
func _traceRelease_run(w *Ref, released bool) {
for p := _traceRelease; p != nil; p = (*_t_traceRelease)(unsafe.Pointer(p.Next())) {
p.probefunc(w, released)
}
}
func traceRelease_Attach(pg *tracing.ProbeGroup, probe func(w *Ref, released bool)) *tracing.Probe {
p := _t_traceRelease{probefunc: probe}
tracing.AttachProbe(pg, (**tracing.Probe)(unsafe.Pointer(&_traceRelease)), &p.Probe)
return &p.Probe
}
// trace export signature
func _trace_exporthash_c65df3d81975319429a288a09095370dace6a87b() {}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment