Merge lp:~rogpeppe/gozk/safe-close into lp:gozk/zookeeper

Proposed by Roger Peppe
Status: Merged
Merged at revision: 31
Proposed branch: lp:~rogpeppe/gozk/safe-close
Merge into: lp:gozk/zookeeper
Diff against target: 523 lines (+301/-13)
5 files modified
close_test.go (+220/-0)
retry_test.go (+3/-3)
suite_test.go (+4/-3)
zk.go (+68/-1)
zk_test.go (+6/-6)
To merge this branch: bzr merge lp:~rogpeppe/gozk/safe-close
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+94812@code.launchpad.net

Description of the change

zookeeper: make Conn.Close safe to call concurrently with other operations.

I am concerned at how complex the test for this is,
relative to the simplicity of the actual code change.
(and it's also potentially fragile on a heavily loaded machine).
That said, I can't think of another way to test the
operations directly, so I'm leaving it in.
Better suggestions welcome.

https://codereview.appspot.com/5699093/

To post a comment you must log in.
Revision history for this message
Roger Peppe (rogpeppe) wrote :

Reviewers: mp+94812_code.launchpad.net,

Message:
Please take a look.

Description:
I am concerned at how complex the test for this is,
relative to the simplicity of the actual code change.
(and it's also potentially fragile on a heavily loaded machine).
That said, I can't think of another way to test the
operations directly, so I'm leaving it in.
Better suggestions welcome.

https://code.launchpad.net/~rogpeppe/gozk/safe-close/+merge/94812

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/5699093/

Affected files:
   A close_test.go
   M retry_test.go
   M suite_test.go
   M zk.go
   M zk_test.go

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/5699093/diff/1001/close_test.go
File close_test.go (right):

https://codereview.appspot.com/5699093/diff/1001/close_test.go#newcode96
close_test.go:96: time.Sleep(0.05e9)
How can you tell this is really unblocking at the right time and
exercising what you intend it to at all?

https://codereview.appspot.com/5699093/

Revision history for this message
Roger Peppe (rogpeppe) wrote :
lp:~rogpeppe/gozk/safe-close updated
33. By Roger Peppe

improve close_test comment.
simplify io.Copy logic.

Revision history for this message
William Reade (fwereade) wrote :

This essentially LGTM; I don't think that it's a serious problem that
the tests can theoretically fail under "enough" load. So it comes down
to a question of practical reliability: for the sake of argument, what
if we shoot for an observed failure rate of <1% on your local machine
under "normal" load, and bump up the sleeps as needed if they turn out
not to be good enough for clean testing in practice (say, on ARM ;))?

https://codereview.appspot.com/5699093/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

LGTM, sorry for the delay.

https://codereview.appspot.com/5699093/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

i forgot to publish this comment.

https://codereview.appspot.com/5699093/diff/1001/close_test.go
File close_test.go (right):

https://codereview.appspot.com/5699093/diff/1001/close_test.go#newcode96
close_test.go:96: time.Sleep(0.05e9)
On 2012/02/27 18:03:17, niemeyer wrote:
> How can you tell this is really unblocking at the right time and
exercising what
> you intend it to at all?

the idea is that any request that requests or changes a zookeeper node
must make at least one round trip to the server. so we interpose a proxy
between the client and the server which can stop all incoming traffic on
demand, thus hopefully blocking the request until we want it to unblock.

we assume that all requests take less than 0.1s to complete, thus when
we wait below, neither of the above goroutines should complete within
the allotted time (the request because it's waiting for a reply from the
server and the close because it's waiting for the request to complete).
if the locking doesn't work, the Close will return early. if the proxy
blocking doesn't work, the request will return early.

when we reenable incoming messages from the server, both goroutines
should complete (we can't tell which completes first - i don't think
that's observable) but i think that the fact that the close blocked
waiting for the request is sufficient.

i've changed the above comment to try to make this clearer.

as i said in the description of the CL, i think this a lot of mechanism
to test something that's quite simple to verify by eye, but i haven't
yet thought of a better way.
one way could be to test Close without testing all the individual
operations - simpler but less complete.

https://codereview.appspot.com/5699093/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

*** Submitted:

zookeeper: make Conn.Close safe to call concurrently with other
operations.

I am concerned at how complex the test for this is,
relative to the simplicity of the actual code change.
(and it's also potentially fragile on a heavily loaded machine).
That said, I can't think of another way to test the
operations directly, so I'm leaving it in.
Better suggestions welcome.

R=niemeyer, fwereade
CC=
https://codereview.appspot.com/5699093

https://codereview.appspot.com/5699093/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'close_test.go'
--- close_test.go 1970-01-01 00:00:00 +0000
+++ close_test.go 2012-02-28 09:18:17 +0000
@@ -0,0 +1,220 @@
1package zookeeper_test
2
3import (
4 "io"
5 . "launchpad.net/gocheck"
6 zk "launchpad.net/gozk/zookeeper"
7 "log"
8 "net"
9 "time"
10)
11
12// requestFuncs holds all the requests that take a read lock
13// on the zk connection except those that don't actually
14// make a round trip to the server.
15var requestFuncs = []func(conn *zk.Conn, path string) error{
16 func(conn *zk.Conn, path string) error {
17 _, err := conn.Create(path, "", 0, nil)
18 return err
19 },
20 func(conn *zk.Conn, path string) error {
21 _, err := conn.Exists(path)
22 return err
23 },
24 func(conn *zk.Conn, path string) error {
25 _, _, err := conn.ExistsW(path)
26 return err
27 },
28 func(conn *zk.Conn, path string) error {
29 _, _, err := conn.Get(path)
30 return err
31 },
32 func(conn *zk.Conn, path string) error {
33 _, _, _, err := conn.GetW(path)
34 return err
35 },
36 func(conn *zk.Conn, path string) error {
37 _, _, err := conn.Children(path)
38 return err
39 },
40 func(conn *zk.Conn, path string) error {
41 _, _, _, err := conn.ChildrenW(path)
42 return err
43 },
44 func(conn *zk.Conn, path string) error {
45 _, err := conn.Set(path, "", 0)
46 return err
47 },
48 func(conn *zk.Conn, path string) error {
49 _, _, err := conn.ACL(path)
50 return err
51 },
52 func(conn *zk.Conn, path string) error {
53 return conn.SetACL(path, []zk.ACL{{
54 Perms: zk.PERM_ALL,
55 Scheme: "digest",
56 Id: "foo",
57 }}, 0)
58 },
59 func(conn *zk.Conn, path string) error {
60 return conn.Delete(path, 0)
61 },
62}
63
64func (s *S) TestConcurrentClose(c *C) {
65 // make sure the server is ready to receive connections.
66 s.init(c)
67
68 // Close should wait until all outstanding requests have
69 // completed before returning. The idea of this test is that
70 // any request that requests or changes a zookeeper node must
71 // make at least one round trip to the server, so we interpose a
72 // proxy between the client and the server which can stop all
73 // incoming traffic on demand, thus blocking the request until
74 // we want it to unblock.
75 //
76 // We assume that all requests take less than 0.1s to complete,
77 // thus when we wait below, neither of the above goroutines
78 // should complete within the allotted time (the request because
79 // it's waiting for a reply from the server and the close
80 // because it's waiting for the request to complete). If the
81 // locking doesn't work, the Close will return early. If the
82 // proxy blocking doesn't work, the request will return early.
83 //
84 // When we reenable incoming messages from the server, both
85 // goroutines should complete. We can't tell which completes
86 // first, but the fact that the close blocked is sufficient to
87 // tell that the locking is working correctly.
88 for i, f := range requestFuncs {
89 c.Logf("iter %d", i)
90 p := newProxy(c, s.zkAddr)
91 conn, watch, err := zk.Dial(p.addr(), 5e9)
92 c.Assert(err, IsNil)
93 c.Assert((<-watch).Ok(), Equals, true)
94
95 // sanity check that the connection is actually
96 // up and running.
97 _, err = conn.Exists("/nothing")
98 c.Assert(err, IsNil)
99
100 p.stopIncoming()
101 reqDone := make(chan bool)
102 closeDone := make(chan bool)
103 go func() {
104 f(conn, "/closetest")
105 reqDone <- true
106 }()
107 go func() {
108 // sleep for long enough for the request to be initiated and the read lock taken.
109 time.Sleep(0.05e9)
110 conn.Close()
111 closeDone <- true
112 }()
113 select {
114 case <-reqDone:
115 c.Fatalf("request %d finished early", i)
116 case <-closeDone:
117 c.Fatalf("request %d close finished early", i)
118 case <-time.After(0.1e9):
119 }
120 p.startIncoming()
121 for reqDone != nil || closeDone != nil {
122 select {
123 case <-reqDone:
124 reqDone = nil
125 case <-closeDone:
126 closeDone = nil
127 case <-time.After(0.4e9):
128 c.Fatalf("request %d timed out waiting for req (%p) and close(%p)", i, reqDone, closeDone)
129 }
130 }
131 p.close()
132 err = f(conn, "/closetest")
133 c.Check(err, Equals, zk.ZCLOSING)
134 }
135}
136
137type proxy struct {
138 stop, start chan bool
139 listener net.Listener
140}
141
142// newProxy will listen on proxyAddr and connect its client to dstAddr, and return
143// a proxy instance that can be used to control the connection.
144func newProxy(c *C, dstAddr string) *proxy {
145 listener, err := net.Listen("tcp", "127.0.0.1:0")
146 c.Assert(err, IsNil)
147 p := &proxy{
148 stop: make(chan bool, 1),
149 start: make(chan bool, 1),
150 listener: listener,
151 }
152
153 go func() {
154 for {
155 client, err := p.listener.Accept()
156 if err != nil {
157 // Ignore the error, because the connection will fail anyway.
158 return
159 }
160 go func() {
161 defer client.Close()
162 server, err := net.Dial("tcp", dstAddr)
163 if err != nil {
164 log.Printf("cannot dial %q: %v", dstAddr, err)
165 return
166 }
167 defer server.Close()
168 go io.Copy(&haltableWriter{
169 w: client,
170 stop: p.stop,
171 start: p.start},
172 server)
173 // When the client is closed, the deferred closes will
174 // take down the other io.Copy too.
175 io.Copy(server, client)
176 }()
177 }
178 }()
179 return p
180}
181
182func (p *proxy) close() error {
183 return p.listener.Close()
184}
185
186func (p *proxy) addr() string {
187 return p.listener.Addr().String()
188}
189
190func (p *proxy) stopIncoming() {
191 if p.stop == nil {
192 panic("cannot stop twice")
193 }
194 p.stop <- true
195 p.stop = nil
196}
197
198func (p *proxy) startIncoming() {
199 if p.start == nil {
200 panic("cannot start twice")
201 }
202 p.start <- true
203 p.start = nil
204}
205
206type haltableWriter struct {
207 w io.Writer
208 stop, start chan bool
209}
210
211func (w *haltableWriter) Write(buf []byte) (int, error) {
212 select {
213 case <-w.stop:
214 w.stop <- true
215 <-w.start
216 w.start <- true
217 default:
218 }
219 return w.w.Write(buf)
220}
0221
=== modified file 'retry_test.go'
--- retry_test.go 2012-02-15 17:18:34 +0000
+++ retry_test.go 2012-02-28 09:18:17 +0000
@@ -25,7 +25,7 @@
2525
26 acl, _, err := conn.ACL("/test")26 acl, _, err := conn.ACL("/test")
27 c.Assert(err, IsNil)27 c.Assert(err, IsNil)
28 c.Assert(acl, Equals, zk.WorldACL(zk.PERM_ALL))28 c.Assert(acl, DeepEquals, zk.WorldACL(zk.PERM_ALL))
29}29}
3030
31func (s *S) TestRetryChangeSetting(c *C) {31func (s *S) TestRetryChangeSetting(c *C) {
@@ -52,7 +52,7 @@
52 // ACL was unchanged by RetryChange().52 // ACL was unchanged by RetryChange().
53 acl, _, err := conn.ACL("/test")53 acl, _, err := conn.ACL("/test")
54 c.Assert(err, IsNil)54 c.Assert(err, IsNil)
55 c.Assert(acl, Equals, zk.WorldACL(zk.PERM_ALL))55 c.Assert(acl, DeepEquals, zk.WorldACL(zk.PERM_ALL))
56}56}
5757
58func (s *S) TestRetryChangeUnchangedValueDoesNothing(c *C) {58func (s *S) TestRetryChangeUnchangedValueDoesNothing(c *C) {
@@ -177,7 +177,7 @@
177 // Should be the new ACL.177 // Should be the new ACL.
178 acl, _, err := conn.ACL("/test")178 acl, _, err := conn.ACL("/test")
179 c.Assert(err, IsNil)179 c.Assert(err, IsNil)
180 c.Assert(acl, Equals, zk.WorldACL(zk.PERM_READ))180 c.Assert(acl, DeepEquals, zk.WorldACL(zk.PERM_READ))
181}181}
182182
183func (s *S) TestRetryChangeErrorInCallback(c *C) {183func (s *S) TestRetryChangeErrorInCallback(c *C) {
184184
=== modified file 'suite_test.go'
--- suite_test.go 2012-02-10 13:33:24 +0000
+++ suite_test.go 2012-02-28 09:18:17 +0000
@@ -31,6 +31,7 @@
31var logLevel = 0 //zk.LOG_ERROR31var logLevel = 0 //zk.LOG_ERROR
3232
33func (s *S) init(c *C) (*zk.Conn, chan zk.Event) {33func (s *S) init(c *C) (*zk.Conn, chan zk.Event) {
34 c.Logf("init dialling %q", s.zkAddr)
34 conn, watch, err := zk.Dial(s.zkAddr, 5e9)35 conn, watch, err := zk.Dial(s.zkAddr, 5e9)
35 c.Assert(err, IsNil)36 c.Assert(err, IsNil)
36 s.handles = append(s.handles, conn)37 s.handles = append(s.handles, conn)
@@ -71,7 +72,7 @@
7172
72func (s *S) SetUpTest(c *C) {73func (s *S) SetUpTest(c *C) {
73 c.Assert(zk.CountPendingWatches(), Equals, 0,74 c.Assert(zk.CountPendingWatches(), Equals, 0,
74 Bug("Test got a dirty watch state before running!"))75 Commentf("Test got a dirty watch state before running!"))
75 zk.SetLogLevel(logLevel)76 zk.SetLogLevel(logLevel)
76}77}
7778
@@ -95,7 +96,7 @@
95 s.handles = make([]*zk.Conn, 0)96 s.handles = make([]*zk.Conn, 0)
9697
97 c.Assert(zk.CountPendingWatches(), Equals, 0,98 c.Assert(zk.CountPendingWatches(), Equals, 0,
98 Bug("Test left live watches behind!"))99 Commentf("Test left live watches behind!"))
99}100}
100101
101// We use the suite set up and tear down to manage a custom ZooKeeper102// We use the suite set up and tear down to manage a custom ZooKeeper
@@ -104,7 +105,7 @@
104 var err error105 var err error
105 s.deadWatches = make(chan bool)106 s.deadWatches = make(chan bool)
106107
107 // N.B. We meed to create a subdirectory because zk.CreateServer108 // N.B. We need to create a subdirectory because zk.CreateServer
108 // insists on creating its own directory.109 // insists on creating its own directory.
109110
110 s.zkTestRoot = c.MkDir() + "/zk"111 s.zkTestRoot = c.MkDir() + "/zk"
111112
=== modified file 'zk.go'
--- zk.go 2012-02-15 17:51:23 +0000
+++ zk.go 2012-02-28 09:18:17 +0000
@@ -32,7 +32,7 @@
32 watchChannels map[uintptr]chan Event32 watchChannels map[uintptr]chan Event
33 sessionWatchId uintptr33 sessionWatchId uintptr
34 handle *C.zhandle_t34 handle *C.zhandle_t
35 mutex sync.Mutex35 mutex sync.RWMutex
36}36}
3737
38// ClientId represents an established ZooKeeper session. It can be38// ClientId represents an established ZooKeeper session. It can be
@@ -429,6 +429,8 @@
429// ClientId returns the client ID for the existing session with ZooKeeper.429// ClientId returns the client ID for the existing session with ZooKeeper.
430// This is useful to reestablish an existing session via ReInit.430// This is useful to reestablish an existing session via ReInit.
431func (conn *Conn) ClientId() *ClientId {431func (conn *Conn) ClientId() *ClientId {
432 conn.mutex.RLock()
433 defer conn.mutex.RUnlock()
432 return &ClientId{*C.zoo_client_id(conn.handle)}434 return &ClientId{*C.zoo_client_id(conn.handle)}
433}435}
434436
@@ -459,6 +461,11 @@
459// unless an error is found. Attempting to retrieve data from a non-existing461// unless an error is found. Attempting to retrieve data from a non-existing
460// node is an error.462// node is an error.
461func (conn *Conn) Get(path string) (data string, stat *Stat, err error) {463func (conn *Conn) Get(path string) (data string, stat *Stat, err error) {
464 conn.mutex.RLock()
465 defer conn.mutex.RUnlock()
466 if conn.handle == nil {
467 return "", nil, ZCLOSING
468 }
462469
463 cpath := C.CString(path)470 cpath := C.CString(path)
464 cbuffer := (*C.char)(C.malloc(bufferSize))471 cbuffer := (*C.char)(C.malloc(bufferSize))
@@ -481,6 +488,11 @@
481// node changes or when critical session events happen. See the488// node changes or when critical session events happen. See the
482// documentation of the Event type for more details.489// documentation of the Event type for more details.
483func (conn *Conn) GetW(path string) (data string, stat *Stat, watch <-chan Event, err error) {490func (conn *Conn) GetW(path string) (data string, stat *Stat, watch <-chan Event, err error) {
491 conn.mutex.RLock()
492 defer conn.mutex.RUnlock()
493 if conn.handle == nil {
494 return "", nil, nil, ZCLOSING
495 }
484496
485 cpath := C.CString(path)497 cpath := C.CString(path)
486 cbuffer := (*C.char)(C.malloc(bufferSize))498 cbuffer := (*C.char)(C.malloc(bufferSize))
@@ -504,6 +516,11 @@
504// Children returns the children list and status from an existing node.516// Children returns the children list and status from an existing node.
505// Attempting to retrieve the children list from a non-existent node is an error.517// Attempting to retrieve the children list from a non-existent node is an error.
506func (conn *Conn) Children(path string) (children []string, stat *Stat, err error) {518func (conn *Conn) Children(path string) (children []string, stat *Stat, err error) {
519 conn.mutex.RLock()
520 defer conn.mutex.RUnlock()
521 if conn.handle == nil {
522 return nil, nil, ZCLOSING
523 }
507524
508 cpath := C.CString(path)525 cpath := C.CString(path)
509 defer C.free(unsafe.Pointer(cpath))526 defer C.free(unsafe.Pointer(cpath))
@@ -529,6 +546,11 @@
529// provided path or when critical session events happen. See the documentation546// provided path or when critical session events happen. See the documentation
530// of the Event type for more details.547// of the Event type for more details.
531func (conn *Conn) ChildrenW(path string) (children []string, stat *Stat, watch <-chan Event, err error) {548func (conn *Conn) ChildrenW(path string) (children []string, stat *Stat, watch <-chan Event, err error) {
549 conn.mutex.RLock()
550 defer conn.mutex.RUnlock()
551 if conn.handle == nil {
552 return nil, nil, nil, ZCLOSING
553 }
532554
533 cpath := C.CString(path)555 cpath := C.CString(path)
534 defer C.free(unsafe.Pointer(cpath))556 defer C.free(unsafe.Pointer(cpath))
@@ -570,6 +592,12 @@
570// stat will contain meta information on the existing node, otherwise592// stat will contain meta information on the existing node, otherwise
571// it will be nil.593// it will be nil.
572func (conn *Conn) Exists(path string) (stat *Stat, err error) {594func (conn *Conn) Exists(path string) (stat *Stat, err error) {
595 conn.mutex.RLock()
596 defer conn.mutex.RUnlock()
597 if conn.handle == nil {
598 return nil, ZCLOSING
599 }
600
573 cpath := C.CString(path)601 cpath := C.CString(path)
574 defer C.free(unsafe.Pointer(cpath))602 defer C.free(unsafe.Pointer(cpath))
575603
@@ -593,6 +621,12 @@
593// is removed. It will also receive critical session events. See the621// is removed. It will also receive critical session events. See the
594// documentation of the Event type for more details.622// documentation of the Event type for more details.
595func (conn *Conn) ExistsW(path string) (stat *Stat, watch <-chan Event, err error) {623func (conn *Conn) ExistsW(path string) (stat *Stat, watch <-chan Event, err error) {
624 conn.mutex.RLock()
625 defer conn.mutex.RUnlock()
626 if conn.handle == nil {
627 return nil, nil, ZCLOSING
628 }
629
596 cpath := C.CString(path)630 cpath := C.CString(path)
597 defer C.free(unsafe.Pointer(cpath))631 defer C.free(unsafe.Pointer(cpath))
598632
@@ -627,6 +661,12 @@
627// from the requested one, such as when a sequence number is appended661// from the requested one, such as when a sequence number is appended
628// to it due to the use of the gozk.SEQUENCE flag.662// to it due to the use of the gozk.SEQUENCE flag.
629func (conn *Conn) Create(path, value string, flags int, aclv []ACL) (pathCreated string, err error) {663func (conn *Conn) Create(path, value string, flags int, aclv []ACL) (pathCreated string, err error) {
664 conn.mutex.RLock()
665 defer conn.mutex.RUnlock()
666 if conn.handle == nil {
667 return "", ZCLOSING
668 }
669
630 cpath := C.CString(path)670 cpath := C.CString(path)
631 cvalue := C.CString(value)671 cvalue := C.CString(value)
632 defer C.free(unsafe.Pointer(cpath))672 defer C.free(unsafe.Pointer(cpath))
@@ -658,6 +698,11 @@
658// It is an error to attempt to set the data of a non-existing node with698// It is an error to attempt to set the data of a non-existing node with
659// this function. In these cases, use Create instead.699// this function. In these cases, use Create instead.
660func (conn *Conn) Set(path, value string, version int) (stat *Stat, err error) {700func (conn *Conn) Set(path, value string, version int) (stat *Stat, err error) {
701 conn.mutex.RLock()
702 defer conn.mutex.RUnlock()
703 if conn.handle == nil {
704 return nil, ZCLOSING
705 }
661706
662 cpath := C.CString(path)707 cpath := C.CString(path)
663 cvalue := C.CString(value)708 cvalue := C.CString(value)
@@ -678,6 +723,12 @@
678// will only succeed if the node is still at this version when the723// will only succeed if the node is still at this version when the
679// node is deleted as an atomic operation.724// node is deleted as an atomic operation.
680func (conn *Conn) Delete(path string, version int) (err error) {725func (conn *Conn) Delete(path string, version int) (err error) {
726 conn.mutex.RLock()
727 defer conn.mutex.RUnlock()
728 if conn.handle == nil {
729 return ZCLOSING
730 }
731
681 cpath := C.CString(path)732 cpath := C.CString(path)
682 defer C.free(unsafe.Pointer(cpath))733 defer C.free(unsafe.Pointer(cpath))
683 rc, cerr := C.zoo_delete(conn.handle, cpath, C.int(version))734 rc, cerr := C.zoo_delete(conn.handle, cpath, C.int(version))
@@ -690,6 +741,12 @@
690// identity data itself. For instance, the "digest" scheme requires741// identity data itself. For instance, the "digest" scheme requires
691// a pair like "username:password" to be provided as the certificate.742// a pair like "username:password" to be provided as the certificate.
692func (conn *Conn) AddAuth(scheme, cert string) error {743func (conn *Conn) AddAuth(scheme, cert string) error {
744 conn.mutex.RLock()
745 defer conn.mutex.RUnlock()
746 if conn.handle == nil {
747 return ZCLOSING
748 }
749
693 cscheme := C.CString(scheme)750 cscheme := C.CString(scheme)
694 ccert := C.CString(cert)751 ccert := C.CString(cert)
695 defer C.free(unsafe.Pointer(cscheme))752 defer C.free(unsafe.Pointer(cscheme))
@@ -714,6 +771,11 @@
714771
715// ACL returns the access control list for path.772// ACL returns the access control list for path.
716func (conn *Conn) ACL(path string) ([]ACL, *Stat, error) {773func (conn *Conn) ACL(path string) ([]ACL, *Stat, error) {
774 conn.mutex.RLock()
775 defer conn.mutex.RUnlock()
776 if conn.handle == nil {
777 return nil, nil, ZCLOSING
778 }
717779
718 cpath := C.CString(path)780 cpath := C.CString(path)
719 defer C.free(unsafe.Pointer(cpath))781 defer C.free(unsafe.Pointer(cpath))
@@ -733,6 +795,11 @@
733795
734// SetACL changes the access control list for path.796// SetACL changes the access control list for path.
735func (conn *Conn) SetACL(path string, aclv []ACL, version int) error {797func (conn *Conn) SetACL(path string, aclv []ACL, version int) error {
798 conn.mutex.RLock()
799 defer conn.mutex.RUnlock()
800 if conn.handle == nil {
801 return ZCLOSING
802 }
736803
737 cpath := C.CString(path)804 cpath := C.CString(path)
738 defer C.free(unsafe.Pointer(cpath))805 defer C.free(unsafe.Pointer(cpath))
739806
=== modified file 'zk_test.go'
--- zk_test.go 2012-02-15 17:51:23 +0000
+++ zk_test.go 2012-02-28 09:18:17 +0000
@@ -312,7 +312,7 @@
312312
313 children, stat, err := conn.Children("/")313 children, stat, err := conn.Children("/")
314 c.Assert(err, IsNil)314 c.Assert(err, IsNil)
315 c.Assert(children, Equals, []string{"zookeeper"})315 c.Assert(children, DeepEquals, []string{"zookeeper"})
316 c.Assert(stat.NumChildren(), Equals, 1)316 c.Assert(stat.NumChildren(), Equals, 1)
317317
318 children, stat, err = conn.Children("/non-existent")318 children, stat, err = conn.Children("/non-existent")
@@ -330,7 +330,7 @@
330330
331 children, stat, watch, err := conn.ChildrenW("/")331 children, stat, watch, err := conn.ChildrenW("/")
332 c.Assert(err, IsNil)332 c.Assert(err, IsNil)
333 c.Assert(children, Equals, []string{"zookeeper"})333 c.Assert(children, DeepEquals, []string{"zookeeper"})
334 c.Assert(stat.NumChildren(), Equals, 1)334 c.Assert(stat.NumChildren(), Equals, 1)
335335
336 select {336 select {
@@ -355,7 +355,7 @@
355 c.Assert(stat.NumChildren(), Equals, 2)355 c.Assert(stat.NumChildren(), Equals, 2)
356356
357 // The ordering is most likely unstable, so this test must be fixed.357 // The ordering is most likely unstable, so this test must be fixed.
358 c.Assert(children, Equals, []string{"test1", "zookeeper"})358 c.Assert(children, DeepEquals, []string{"test1", "zookeeper"})
359359
360 select {360 select {
361 case <-watch:361 case <-watch:
@@ -481,7 +481,7 @@
481 defer zk2.Close()481 defer zk2.Close()
482 clientId2 := zk2.ClientId()482 clientId2 := zk2.ClientId()
483483
484 c.Assert(clientId1, Equals, clientId2)484 c.Assert(clientId1, DeepEquals, clientId2)
485}485}
486486
487// Surprisingly for some (including myself, initially), the watch487// Surprisingly for some (including myself, initially), the watch
@@ -512,7 +512,7 @@
512512
513 acl, stat, err := conn.ACL("/test")513 acl, stat, err := conn.ACL("/test")
514 c.Assert(err, IsNil)514 c.Assert(err, IsNil)
515 c.Assert(acl, Equals, zk.WorldACL(zk.PERM_ALL))515 c.Assert(acl, DeepEquals, zk.WorldACL(zk.PERM_ALL))
516 c.Assert(stat, NotNil)516 c.Assert(stat, NotNil)
517 c.Assert(stat.Version(), Equals, 0)517 c.Assert(stat.Version(), Equals, 0)
518518
@@ -538,7 +538,7 @@
538538
539 acl, _, err := conn.ACL("/test")539 acl, _, err := conn.ACL("/test")
540 c.Assert(err, IsNil)540 c.Assert(err, IsNil)
541 c.Assert(acl, Equals, zk.WorldACL(zk.PERM_READ))541 c.Assert(acl, DeepEquals, zk.WorldACL(zk.PERM_READ))
542}542}
543543
544func (s *S) TestAddAuth(c *C) {544func (s *S) TestAddAuth(c *C) {

Subscribers

People subscribed via source and target branches