Skip to content

Commit

Permalink
delegates: keep a goroutine alive during the lifecycle of delegates
Browse files Browse the repository at this point in the history
There's an error in the Go runtime that causes a deadlock error whenever
we are waiting for a delegate callback to happen. This is because Go is
not able to detect that we are going to receive a syscall in the future.
See this for more information: golang/go#55015
  • Loading branch information
jagobagascon committed Sep 20, 2023
1 parent e4d6638 commit faacaf9
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
58 changes: 58 additions & 0 deletions internal/codegen/templates/delegate.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ var callbacks{{.Name}} = &{{.Name | toLower}}CallbacksMap {
callbacks: make(map[unsafe.Pointer]{{.Name}}Callback),
}

var releaseChans{{.Name}} = &{{.Name | toLower}}ReleaseChannelsMap {
mu: &sync.Mutex{},
chans: make(map[unsafe.Pointer]chan struct{}),
}

func New{{.Name}}(iid *ole.GUID, callback {{.Name}}Callback) *{{.Name}} {
size := unsafe.Sizeof(*(*{{.Name}})(nil))
instPtr := kernel32.Malloc(size)
Expand All @@ -42,6 +47,9 @@ func New{{.Name}}(iid *ole.GUID, callback {{.Name}}Callback) *{{.Name}} {

callbacks{{.Name}}.add(unsafe.Pointer(inst), callback)

// See the docs in the releaseChans{{.Name}} struct
releaseChans{{.Name}}.create(unsafe.Pointer(inst))

inst.addRef()
return inst
}
Expand Down Expand Up @@ -127,6 +135,11 @@ func (instance *{{.Name}}) Release() uint64 {
// We're done.
instancePtr := unsafe.Pointer(instance)
callbacks{{.Name}}.delete(instancePtr)

// stop release channels used to avoid
// https://github.com/golang/go/issues/55015
releaseChans{{.Name}}.release(instancePtr)

kernel32.Free(instancePtr)
}
return rem
Expand Down Expand Up @@ -158,3 +171,48 @@ func (m *{{.Name | toLower}}CallbacksMap) delete(p unsafe.Pointer) {

delete(m.callbacks, p)
}

// typedEventHandlerReleaseChannelsMap keeps a map with channels
// used to keep a goroutine alive during the lifecycle of this object.
// This is required to avoid causing a deadlock error.
// See this: https://github.com/golang/go/issues/55015
type {{.Name | toLower}}ReleaseChannelsMap struct {
mu *sync.Mutex
chans map[unsafe.Pointer]chan struct{}
}

func (m *{{.Name | toLower}}ReleaseChannelsMap) create(p unsafe.Pointer) {
m.mu.Lock()
defer m.mu.Unlock()

c := make(chan struct{})
m.chans[p] = c

go func() {
// we need a timer to trick the go runtime into
// thinking there's still something going on here
// but we are only really interested in <-c
t := time.NewTimer(time.Minute)
for {
select {
case <-t.C:
t.Reset(time.Minute)
case <-c:
if !t.Stop(){
<-t.C
}
return
}
}
}()
}

func (m *{{.Name | toLower}}ReleaseChannelsMap) release(p unsafe.Pointer) {
m.mu.Lock()
defer m.mu.Unlock()

if c, ok := m.chans[p]; ok {
close(c)
delete(m.chans, p)
}
}
59 changes: 59 additions & 0 deletions windows/foundation/asyncoperationcompletedhandler.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions windows/foundation/typedeventhandler.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit faacaf9

Please sign in to comment.