Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions systemd/dbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"context"
"errors"
"fmt"
"math/rand/v2"
"sync"
"time"

systemdDbus "github.com/coreos/go-systemd/v22/dbus"
dbus "github.com/godbus/dbus/v5"
"golang.org/x/sys/unix"
)

var (
Expand Down Expand Up @@ -64,10 +67,27 @@ func (d *dbusConnManager) getConnection() (*systemdDbus.Conn, error) {
}

func (d *dbusConnManager) newConnection() (*systemdDbus.Conn, error) {
if dbusRootless {
return newUserSystemdDbus()
newDbusConn := func() (*systemdDbus.Conn, error) {
if dbusRootless {
return newUserSystemdDbus()
}
return systemdDbus.NewWithContext(context.TODO())
}

var err error
for retry := range 7 {
var conn *systemdDbus.Conn
conn, err = newDbusConn()
if !errors.Is(err, unix.EAGAIN) {
return conn, err
}
// Exponential backoff (100ms * 2^attempt + ~12.5% jitter).
// At most we would expect 15 seconds of delay with 7 attempts.
delay := 100 * time.Millisecond << retry
delay += time.Duration(rand.Int64N(1 + (delay.Milliseconds() >> 3)))
time.Sleep(delay)
}
return systemdDbus.NewWithContext(context.TODO())
return nil, fmt.Errorf("dbus connection failed after several retries: %w", err)
}

// resetConnection resets the connection to its initial state
Expand Down
55 changes: 55 additions & 0 deletions systemd/dbus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package systemd

import (
"context"
"os"
"sync"
"testing"
)

func TestParallelConnection(t *testing.T) {
Copy link
Member

@cyphar cyphar Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funnily enough, this test actually crashes Firefox on my machine if you run it in rootless mode as your user:

[1522754.178417] [   T3250] wireplumber[3250]: segfault at 8 ip 00007f8b0097315d sp 00007ffd1e267fe8 error 4 in libgobject-2.0.so.0.8600.0[3d15d,7f8b00946000+36000] likely on CPU 14 (core 7, socket 0)
[1522754.178426] [   T3250] Code: 0f 1f 44 00 00 48 c1 ed 02 48 8d 05 5d 62 02 00 48 8b 34 e8 e9 48 ff ff ff 0f 1f 40 00 48 85 ff 74 4b 48 8b 07 48 85 c0 74 43 <48> 8b 00 48 3d fc 03 00 00 77 28 48 8d 15 31 62 02 00 48 c1 e8 02

if !IsRunningSystemd() {
t.Skip("Test requires systemd.")
}
var dms []*dbusConnManager
for range 600 {
dms = append(dms, newDbusConnManager(os.Geteuid() != 0))
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

var (
doneWg sync.WaitGroup
startCh = make(chan struct{})
errCh = make(chan error, 1)
)
for _, dm := range dms {
doneWg.Add(1)
go func(dm *dbusConnManager) {
defer doneWg.Done()
select {
case <-ctx.Done():
return
case <-startCh:
_, err := dm.newConnection()
if err != nil {
// Only bother trying to send the first error.
select {
case errCh <- err:
default:
}
cancel()
}
}
}(dm)
}
close(startCh) // trigger all connection attempts
doneWg.Wait()

select {
case err := <-errCh:
t.Fatal(err)
default:
}
}
Loading