Commit e39e6682 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent dea433be
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
// Copyright (C) 2018-2020 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// based on:
// https://groups.google.com/d/msg/golang-nuts/PYWxjT2v6ps/dL71oJk1mXEJ
......@@ -24,15 +24,20 @@
// Package weak provides weak references for Go.
package weak
//go:generate gotrace gen .
import (
"fmt"
"runtime"
"sync"
// "time"
"unsafe"
)
// iface is how Go runtime represents an interface.
//
// NOTE layout must be synchronized to Go runtime representation.
// NOTE correctness depends on non-moving property of Go GC.
type iface struct {
typ uintptr // type
data uintptr // data
......@@ -63,6 +68,8 @@ type Ref struct {
state weakRefState
}
//trace:event traceRelease(w *Ref, released bool)
// NewRef creates new weak reference pointing to obj.
//
// TODO + onrelease callback?
......@@ -77,6 +84,12 @@ func NewRef(obj interface{}) *Ref {
var release func(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
// scheduled us to run as finalizer. During the time till we
// actually run, Ref.Get might have been come to run and
......@@ -87,16 +100,21 @@ func NewRef(obj interface{}) *Ref {
if w.state == objGot {
w.state = objLive
runtime.SetFinalizer(obj, release)
traceRelease(w, false)
} else {
w.state = objReleased
traceRelease(w, true)
}
w.mu.Unlock()
}
runtime.SetFinalizer(obj, release)
return w
}
//trace:event traceGotPre(w *Ref)
// Get returns object pointed to by this weak reference.
//
// If original object is still alive - it is returned.
......@@ -106,6 +124,12 @@ func (w *Ref) Get() (obj interface{}) {
if w.state != objReleased {
w.state = objGot
traceGotPre(w)
//time.Sleep(100*time.Nanosecond)
//time.Sleep(10*time.Millisecond)
//runtime.GC()
//runtime.GC()
// recreate interface{} from saved words.
// XXX do writes as pointers so that compiler emits write barriers to notify GC?
i := (*iface)(unsafe.Pointer(&obj))
......
......@@ -24,6 +24,8 @@ import (
"testing"
"time"
"unsafe"
"lab.nexedi.com/kirr/go123/tracing"
)
// verify that interface <-> iface works ok.
......@@ -60,6 +62,22 @@ func TestWeakRef(t *testing.T) {
w := NewRef(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{}) {
t.Helper()
if a != b {
......@@ -68,24 +86,39 @@ func TestWeakRef(t *testing.T) {
}
// perform GC + give finalizers a chance to run.
GC := func() {
GC := func(expectRelease bool) {
t.Helper()
runtime.GC()
// GC only queues finalizers, not runs them directly. Give it
// 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.Get(), p)
assertEq(w.state, objGot)
GC()
GC(false)
assertEq(w.state, objGot) // fin has not been run at all (p is live)
assertEq(w.Get(), p)
assertEq(w.state, objGot)
p = nil
GC()
GC(true)
assertEq(w.state, objLive) // fin ran and downgraded got -> live
switch p_ := w.Get().(type) {
default:
......@@ -97,10 +130,10 @@ func TestWeakRef(t *testing.T) {
}
assertEq(w.state, objGot)
GC()
GC(true)
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.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