Skip to content

Connect deadlocks on MacOS #118

@sethterashima

Description

@sethterashima

Current Connect implementation deadlocks on Darwin (https://github.com/go-ble/ble/blob/master/gatt.go#L126-L149):

func Connect(ctx context.Context, f AdvFilter) (Client, error) {
	ctx2, cancel := context.WithCancel(ctx)
	go func() {
		select {
		case <-ctx.Done():
			cancel()
		case <-ctx2.Done():
		}
	}()

	ch := make(chan Advertisement)
	fn := func(a Advertisement) {
		cancel()
		ch <- a
	}
	if err := Scan(ctx2, false, fn, f); err != nil {
		if err != context.Canceled {
			return nil, errors.Wrap(err, "can't scan")
		}
	}

	cln, err := Dial(ctx, (<-ch).Addr())
	return cln, errors.Wrap(err, "can't dial")
}

Note that ch is unbuffered. This means when fn is executed, the caller will block until a reader is ready; the only reader is the call to Dial in the above snippet. Therefore if Scan ends up blocking while fn tries to write to the channel, we have a deadlock.

Sure enough, in the Darwin Scan implementation, we have the following code:

	// Begin processing responses from ch (events received by DidDiscoverPeripheral).
	go func() {
		for r := range ch {
			h(r)
			wg.Done()
		}
	}()

The handler h in this snippet is either fn or a wrapper around fn, depending on whether or not the call to Connect includes a filter. Therefore h blocks until the call to Dial, and the call to wg.Done() never executes. Since the waitgroup never unblocks, Scan never finishes and Dial is never called.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions