Repository: godbus/dbus
Pull request #212 (2020-09-22 08:27:42)

Add AddMatchSignal and RemoveMatchSignal versions that accept a context
This adds AddMatchSignalContext(), RemoveMatchSignalContext() to allow
passing a context to the underlying DBus call. Even though AddMatch is
not a long running call, this is handy when
subscribing to signals from request context or while debugging
connection issues.

Please let me know if you have any issues with this pull request.

jsouthworth: LGTM

Patch:
diff --git a/conn.go b/conn.go
index 3507aa6..f959d21 100644
--- a/conn.go
+++ b/conn.go
@@ -586,8 +586,14 @@ func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
 // AddMatchSignal registers the given match rule to receive broadcast
 // signals based on their contents.
 func (conn *Conn) AddMatchSignal(options ...MatchOption) error {
+	return conn.AddMatchSignalContext(context.Background(), options...)
+}
+
+// AddMatchSignalContext acts like AddMatchSignal but takes a context.
+func (conn *Conn) AddMatchSignalContext(ctx context.Context, options ...MatchOption) error {
 	options = append([]MatchOption{withMatchType("signal")}, options...)
-	return conn.busObj.Call(
+	return conn.busObj.CallWithContext(
+		ctx,
 		"org.freedesktop.DBus.AddMatch", 0,
 		formatMatchOptions(options),
 	).Store()
@@ -595,8 +601,14 @@ func (conn *Conn) AddMatchSignal(options ...MatchOption) error {
 
 // RemoveMatchSignal removes the first rule that matches previously registered with AddMatchSignal.
 func (conn *Conn) RemoveMatchSignal(options ...MatchOption) error {
+	return conn.RemoveMatchSignalContext(context.Background(), options...)
+}
+
+// RemoveMatchSignalContext acts like RemoveMatchSignal but takes a context.
+func (conn *Conn) RemoveMatchSignalContext(ctx context.Context, options ...MatchOption) error {
 	options = append([]MatchOption{withMatchType("signal")}, options...)
-	return conn.busObj.Call(
+	return conn.busObj.CallWithContext(
+		ctx,
 		"org.freedesktop.DBus.RemoveMatch", 0,
 		formatMatchOptions(options),
 	).Store()


Test patch:
diff --git a/conn_test.go b/conn_test.go
index 9ab34c7..ff23b71 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -206,6 +206,69 @@ func TestCloseChannelAfterRemoveSignal(t *testing.T) {
 	close(ch)
 }
 
+func TestAddAndRemoveMatchSignalContext(t *testing.T) {
+	conn, err := ConnectSessionBus()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer conn.Close()
+
+	sigc := make(chan *Signal, 1)
+	conn.Signal(sigc)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+	// try to subscribe to a made up signal with an already canceled context
+	if err = conn.AddMatchSignalContext(
+		ctx,
+		WithMatchInterface("org.test"),
+		WithMatchMember("CtxTest"),
+	); err == nil {
+		t.Fatal("call on canceled context did not fail")
+	}
+
+	// subscribe to the signal with background context
+	if err = conn.AddMatchSignalContext(
+		context.Background(),
+		WithMatchInterface("org.test"),
+		WithMatchMember("CtxTest"),
+	); err != nil {
+		t.Fatal(err)
+	}
+
+	// try to unsubscribe with an already canceled context
+	if err = conn.RemoveMatchSignalContext(
+		ctx,
+		WithMatchInterface("org.test"),
+		WithMatchMember("CtxTest"),
+	); err == nil {
+		t.Fatal("call on canceled context did not fail")
+	}
+
+	// check that signal is still delivered
+	if err = conn.Emit("/", "org.test.CtxTest"); err != nil {
+		t.Fatal(err)
+	}
+	if sig := waitSignal(sigc, "org.test.CtxTest", time.Second); sig == nil {
+		t.Fatal("signal receive timed out")
+	}
+
+	// unsubscribe from the signal
+	if err = conn.RemoveMatchSignalContext(
+		context.Background(),
+		WithMatchInterface("org.test"),
+		WithMatchMember("CtxTest"),
+	); err != nil {
+		t.Fatal(err)
+	}
+	if err = conn.Emit("/", "org.test.CtxTest"); err != nil {
+		t.Fatal(err)
+	}
+	if sig := waitSignal(sigc, "org.test.CtxTest", time.Second); sig != nil {
+		t.Fatalf("unsubscribed from %q signal, but received %#v", "org.test.CtxTest", sig)
+	}
+}
+
 func TestAddAndRemoveMatchSignal(t *testing.T) {
 	conn, err := ConnectSessionBus()
 	if err != nil {