Skip to content

Commit e029ee1

Browse files
b0bbywanclaude
andcommitted
fix(login1,bluetooth): bound D-Bus calls with a real deadline
Same no-op callWithTimeout as mpris: it read call.Err after obj.Call had already blocked for the reply. Route every call through CallWithContext with the backend timeout. Device1.Connect keeps the pairing timeout since a first connect bonds the device and routinely outlasts the 5s default. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F9qZzw2ePsBw6ZeUPnzTDz
1 parent 6a49487 commit e029ee1

2 files changed

Lines changed: 40 additions & 57 deletions

File tree

backend/bluetooth/dbus.go

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package bluetooth
22

33
import (
4+
"context"
5+
"errors"
46
"fmt"
57
"regexp"
68
"strings"
@@ -14,43 +16,37 @@ import (
1416

1517
var macRegex = regexp.MustCompile(`^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$`)
1618

17-
// callWithTimeout executes a D-Bus call with timeout
18-
func callWithTimeout(call *dbus.Call, timeout time.Duration) error {
19-
done := make(chan error, 1)
20-
21-
go func() {
22-
done <- call.Err
23-
}()
24-
25-
select {
26-
case err := <-done:
27-
return err
28-
case <-time.After(timeout):
29-
return &dbusTimeoutError{}
19+
// call issues a method call bounded by timeout. The deadline has to be on the
20+
// call itself: obj.Call blocks until a reply, so wrapping it afterwards never
21+
// times out.
22+
func call(obj dbus.BusObject, timeout time.Duration, method string, args ...interface{}) *dbus.Call {
23+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
24+
defer cancel()
25+
c := obj.CallWithContext(ctx, method, 0, args...)
26+
if errors.Is(c.Err, context.DeadlineExceeded) {
27+
c.Err = &dbusTimeoutError{}
3028
}
29+
return c
3130
}
3231

33-
// callWithTimeout receiver method for BluetoothBackend
34-
func (b *BluetoothBackend) callWithTimeout(call *dbus.Call) error {
35-
return callWithTimeout(call, b.timeout)
32+
func (b *BluetoothBackend) call(obj dbus.BusObject, method string, args ...interface{}) *dbus.Call {
33+
return call(obj, b.timeout, method, args...)
3634
}
3735

38-
// callMethod calls a method on an object with timeout
3936
func (b *BluetoothBackend) callMethod(obj dbus.BusObject, method string, args ...interface{}) error {
40-
return b.callWithTimeout(obj.Call(method, 0, args...))
37+
return b.call(obj, method, args...).Err
4138
}
4239

4340
func (b *BluetoothBackend) setProperty(obj dbus.BusObject, iface, prop string, value interface{}) error {
44-
call := obj.Call(DBUS_PROP_SET, 0, iface, prop, dbus.MakeVariant(value))
45-
return b.callWithTimeout(call)
41+
return b.call(obj, DBUS_PROP_SET, iface, prop, dbus.MakeVariant(value)).Err
4642
}
4743

4844
// getProperty retrieves a property from D-Bus for a given busName
4945
func (b *BluetoothBackend) getProperty(obj dbus.BusObject, iface, prop string) (dbus.Variant, error) {
5046
var v dbus.Variant
51-
call := obj.Call(DBUS_PROP_GET, 0, iface, prop)
52-
if err := b.callWithTimeout(call); err != nil {
53-
return dbus.Variant{}, err
47+
call := b.call(obj, DBUS_PROP_GET, iface, prop)
48+
if call.Err != nil {
49+
return dbus.Variant{}, call.Err
5450
}
5551
if err := call.Store(&v); err != nil {
5652
return dbus.Variant{}, err
@@ -67,14 +63,7 @@ func (b *BluetoothBackend) adapter() dbus.BusObject {
6763
}
6864

6965
func (b *BluetoothBackend) setAdapterProp(prop string, value interface{}) error {
70-
call := b.adapter().Call(
71-
DBUS_PROP_SET,
72-
0,
73-
BLUETOOTH_ADAPTER,
74-
prop,
75-
dbus.MakeVariant(value),
76-
)
77-
return b.callWithTimeout(call)
66+
return b.setProperty(b.adapter(), BLUETOOTH_ADAPTER, prop, value)
7867
}
7968

8069
func extractBool(v dbus.Variant) (bool, bool) {
@@ -255,8 +244,10 @@ func (b *BluetoothBackend) setDiscoveryFilter() error {
255244
return nil
256245
}
257246

247+
// connectDevice gets the pairing deadline: a first connect bonds the device
248+
// and takes several seconds, well past the generic call timeout.
258249
func (b *BluetoothBackend) connectDevice(path dbus.ObjectPath) error {
259-
return b.callMethod(b.getObj(BLUETOOTH_PREFIX, string(path)), DEVICE_CONNECT)
250+
return call(b.getObj(BLUETOOTH_PREFIX, string(path)), b.pairingTimeout, DEVICE_CONNECT).Err
260251
}
261252

262253
func (b *BluetoothBackend) disconnectDevice(path dbus.ObjectPath) error {
@@ -356,7 +347,7 @@ func (b *BluetoothBackend) SetTimeOut(prop string) error {
356347
func (b *BluetoothBackend) getManagedObjects() (map[dbus.ObjectPath]map[string]map[string]dbus.Variant, error) {
357348
objManager := b.getObj(BLUETOOTH_PREFIX, "/")
358349
var managedObjects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
359-
if err := objManager.Call(MANAGED_OBJECTS, 0).Store(&managedObjects); err != nil {
350+
if err := b.call(objManager, MANAGED_OBJECTS).Store(&managedObjects); err != nil {
360351
logger.Warn("[bluetooth] failed to query BlueZ managed objects: %v", err)
361352
return nil, err
362353
}

backend/login1/dbus.go

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,34 @@
11
package login1
22

33
import (
4-
"time"
4+
"context"
5+
"errors"
56

67
"github.com/godbus/dbus/v5"
78
)
89

9-
// callWithTimeout executes a D-Bus call with timeout
10-
func callWithTimeout(call *dbus.Call, timeout time.Duration) error {
11-
done := make(chan error, 1)
12-
13-
go func() {
14-
done <- call.Err
15-
}()
16-
17-
select {
18-
case err := <-done:
19-
return err
20-
case <-time.After(timeout):
21-
return &dbusTimeoutError{}
10+
// call issues a method call bounded by the backend timeout. The deadline has
11+
// to be on the call itself: obj.Call blocks until a reply, so wrapping it
12+
// afterwards never times out.
13+
func (l *Login1Backend) call(obj dbus.BusObject, method string, args ...interface{}) *dbus.Call {
14+
ctx, cancel := context.WithTimeout(context.Background(), l.timeout)
15+
defer cancel()
16+
c := obj.CallWithContext(ctx, method, 0, args...)
17+
if errors.Is(c.Err, context.DeadlineExceeded) {
18+
c.Err = &dbusTimeoutError{}
2219
}
20+
return c
2321
}
2422

2523
func (l *Login1Backend) callMethod(busName, method string, args ...interface{}) error {
26-
obj := l.conn.Object(busName, LOGIN1_PATH)
27-
return l.callWithTimeout(obj.Call(method, 0, args...))
28-
}
29-
30-
func (l *Login1Backend) callWithTimeout(call *dbus.Call) error {
31-
return callWithTimeout(call, l.timeout)
24+
return l.call(l.conn.Object(busName, LOGIN1_PATH), method, args...).Err
3225
}
3326

3427
// callDBusMethod calls a D-Bus method and returns the call for further processing
3528
func (l *Login1Backend) callDBusMethod(method string, args ...interface{}) (*dbus.Call, error) {
36-
obj := l.conn.Object(LOGIN1_PREFIX, LOGIN1_PATH)
37-
call := obj.Call(method, 0, args...)
38-
if err := l.callWithTimeout(call); err != nil {
39-
return nil, err
29+
call := l.call(l.conn.Object(LOGIN1_PREFIX, LOGIN1_PATH), method, args...)
30+
if call.Err != nil {
31+
return nil, call.Err
4032
}
4133
return call, nil
4234
}

0 commit comments

Comments
 (0)