Merge lp:~mvo/go-dbus/dbus-service into lp:go-dbus/v1

Proposed by Michael Vogt
Status: Needs review
Proposed branch: lp:~mvo/go-dbus/dbus-service
Merge into: lp:go-dbus/v1
Diff against target: 204 lines (+183/-1)
3 files modified
dbus.go (+1/-1)
service.go (+87/-0)
service_test.go (+95/-0)
To merge this branch: bzr merge lp:~mvo/go-dbus/dbus-service
Reviewer Review Type Date Requested Status
Go D-Bus Hackers Pending
Review via email: mp+245736@code.launchpad.net

Description of the change

This branch adds a new DBusService helper that can be used to export a existing type over dbus. Its loosely modeled after the python dbus way of exporting objects.

The idea is that you can simply do:
"""
type Impl struct {
}
func (m *Impl) Something() error {
 return nil
}
ser := NewDBusService(dbus.Connect(dbus.SessionBus), INTERFACE, OBJECT_PATH, BUS_NAME, impl)
"""
and this makes it possible to call Impl.Something() over dbus.

This diff is mostly meant to start a discussion if this is the right approach. If you guys like it the error checking of the reflect code needs to be improved, the test needs to test for various error conditions (wrong parameter count, wrong types etc) and it would be nice if it would generate introspect data automatically for the dbus introspection interface.

In r135,136 it also contains a testcase and a fix for a hang that can happen when a dbus method sends a signal with a active watcher before the reply of the method is send.

To post a comment you must log in.

Unmerged revisions

136. By Michael Vogt

dbus.go: fix previous hang by running the watcher callbacks in a go routine

135. By Michael Vogt

add FAILING test (it hangs) that shows how go-dbus hangs when a method is sending a Signal in the dbus message before sending a reply msg

134. By Michael Vogt

Add DBusService wrapper

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dbus.go'
2--- dbus.go 2014-11-19 19:24:28 +0000
3+++ dbus.go 2015-01-30 09:31:50 +0000
4@@ -238,7 +238,7 @@
5 watches := p.signalMatchRules.FindMatches(msg)
6 p.handlerMutex.Unlock()
7 for _, watch := range watches {
8- watch.cb(msg)
9+ go watch.cb(msg)
10 }
11 }
12 return nil
13
14=== added file 'service.go'
15--- service.go 1970-01-01 00:00:00 +0000
16+++ service.go 2015-01-30 09:31:50 +0000
17@@ -0,0 +1,87 @@
18+package dbus
19+
20+import (
21+ "fmt"
22+ "log"
23+ "reflect"
24+ "runtime"
25+)
26+
27+type DBusService struct {
28+ conn *Connection
29+ msgChan chan *Message
30+
31+ BusInterface string
32+ BusPath string
33+ BusName string
34+
35+ actor interface{}
36+}
37+
38+func NewDBusService(conn *Connection, interf, path, name string, actor interface{}) *DBusService {
39+ s := &DBusService{
40+ conn: conn,
41+ msgChan: make(chan *Message),
42+ BusInterface: interf,
43+ BusPath: path,
44+ BusName: name,
45+ actor: actor}
46+ runtime.SetFinalizer(s, cleanupDBusService)
47+
48+ nameOnBus := conn.RequestName(name, NameFlagDoNotQueue)
49+ err := <-nameOnBus.C
50+ if err != nil {
51+ fmt.Errorf("bus name coule not be taken %s", err)
52+ return nil
53+ }
54+ go s.watchBus()
55+ conn.RegisterObjectPath(ObjectPath(path), s.msgChan)
56+ return s
57+}
58+
59+func (s *DBusService) Send(msg *Message) (err error) {
60+ return s.conn.Send(msg)
61+}
62+
63+func (s *DBusService) watchBus() {
64+ for msg := range s.msgChan {
65+ var reply *Message
66+ switch {
67+ case msg.Interface == s.BusInterface:
68+ methodName := msg.Member
69+ m := reflect.ValueOf(s.actor).MethodByName(methodName)
70+ if !m.IsValid() {
71+ reply = NewErrorMessage(msg, "method-not-found", fmt.Sprintf("method %s not found for %s", methodName, s.actor))
72+ break
73+ }
74+ allArgs := msg.AllArgs()
75+ params := make([]reflect.Value, len(allArgs))
76+ for i, arg := range allArgs {
77+ params[i] = reflect.ValueOf(arg)
78+ }
79+ // FIMXE: check if params match the method signature
80+ ret := m.Call(params)
81+ // FIXME: check we always get at least one value
82+ // back
83+ errVal := ret[len(ret)-1]
84+ if !errVal.IsNil() {
85+ reply = NewErrorMessage(msg, "method-returned-error", fmt.Sprintf("%v", reflect.ValueOf(errVal)))
86+ break
87+ }
88+ reply = NewMethodReturnMessage(msg)
89+ for i := 0; i < len(ret)-1; i++ {
90+ reply.AppendArgs(ret[i].Interface())
91+ }
92+ default:
93+ log.Println("unknown method call %v", msg)
94+ }
95+ if err := s.conn.Send(reply); err != nil {
96+ log.Println("could not send reply:", err)
97+ }
98+ }
99+}
100+
101+func cleanupDBusService(s *DBusService) {
102+ s.conn.UnregisterObjectPath(ObjectPath(s.BusPath))
103+ close(s.msgChan)
104+}
105
106=== added file 'service_test.go'
107--- service_test.go 1970-01-01 00:00:00 +0000
108+++ service_test.go 2015-01-30 09:31:50 +0000
109@@ -0,0 +1,95 @@
110+package dbus
111+
112+import (
113+ "fmt"
114+
115+ . "launchpad.net/gocheck"
116+)
117+
118+const (
119+ INTERFACE = "com.example.Testing"
120+ OBJECT_PATH = "/Testing"
121+ BUS_NAME = INTERFACE
122+ TEST_SIGNAL = "TestSignalName"
123+)
124+
125+type TestService struct {
126+ TrivialCalled bool
127+ service *DBusService
128+}
129+
130+func (m *TestService) Trivial() error {
131+ m.TrivialCalled = true
132+ return nil
133+}
134+
135+func (m *TestService) ComplexReturn() (map[string]string, error) {
136+ ret := make(map[string]string)
137+ ret["pi"] = "3.14"
138+ return ret, nil
139+}
140+
141+func (m *TestService) WithArgs(v string) (string, error) {
142+ return fmt.Sprintf("got: %s", v), nil
143+}
144+
145+func (m *TestService) SendsSignal(v string) (error) {
146+ sig := NewSignalMessage(OBJECT_PATH, INTERFACE, TEST_SIGNAL)
147+ sig.AppendArgs(v)
148+ if err := m.service.Send(sig); err != nil {
149+ panic(err)
150+ }
151+ return nil
152+}
153+
154+
155+func (s *S) TestDBusService(c *C) {
156+
157+ // setUp
158+ conn, err := Connect(SessionBus)
159+ c.Assert(err, IsNil)
160+ c.Assert(conn, NotNil)
161+ defer conn.Close()
162+
163+ impl := new(TestService)
164+ ser := NewDBusService(conn, INTERFACE, OBJECT_PATH, BUS_NAME, impl)
165+ c.Assert(ser, NotNil)
166+ impl.service = ser
167+
168+ // t1: trivial
169+ proxy := conn.Object(BUS_NAME, OBJECT_PATH)
170+ c.Assert(proxy, NotNil)
171+ _, err = proxy.Call(BUS_NAME, "Trivial")
172+ c.Assert(err, IsNil)
173+ c.Assert(impl.TrivialCalled, Equals, true)
174+
175+ // t2: map return
176+ msg, err := proxy.Call(BUS_NAME, "ComplexReturn")
177+ c.Assert(err, IsNil)
178+ var ret map[string]string
179+ msg.Args(&ret)
180+ c.Assert(ret, DeepEquals, map[string]string{"pi": "3.14"})
181+
182+ // t3: pass args
183+ msg, err = proxy.Call(BUS_NAME, "WithArgs", "meep")
184+ c.Assert(err, IsNil)
185+ var ret2 string
186+ msg.Args(&ret2)
187+ c.Assert(ret2, Equals, "got: meep")
188+
189+ // t4: send signal from method
190+ watch, err := conn.WatchSignal(&MatchRule{
191+ Type: TypeSignal,
192+ Sender: BUS_NAME,
193+ Interface: INTERFACE,
194+ Member: TEST_SIGNAL})
195+ c.Assert(err, IsNil)
196+ defer watch.Cancel()
197+
198+ msg, err = proxy.Call(BUS_NAME, "SendsSignal", "meep")
199+ c.Assert(err, IsNil)
200+ sigReplyMsg := <- watch.C
201+ var sigReply string
202+ sigReplyMsg.Args(&sigReply)
203+ c.Assert(sigReply, Equals, "meep")
204+}

Subscribers

People subscribed via source and target branches

to all changes: