The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 19
META.json 11
META.yml 11
ZooKeeper.pm 210
ZooKeeper.xs 121
t/45_class.t 97
6 files changed (This is a version diff) 1549
@@ -70,7 +70,15 @@ Revision history
         - add str_error() call for translating errors to human-readable strings
         - add state() method from #72702
 
-0.39 May 13, 2014
+0.39 May 13, 2015
         - add ZSESSIONMOVED error constant
         - bump ZooKeeper version dependency to 3.2.0
         - Fix handling of non-integer-seconds watch timeouts
+
+0.40 May 23, 2015
+        - update docs better explain Net::ZooKeeper constructor error handling
+        - fix test failure from interference with other nodes
+
+0.41 Jun 17, 2015
+        - fix deadlocks caused by premature watch garbage collection. (#5)
+        - added set_ignore_session_events function
@@ -43,5 +43,5 @@
          "web" : "https://github.com/mark-5/p5-net-zookeeper"
       }
    },
-   "version" : "0.39"
+   "version" : "0.41"
 }
@@ -21,4 +21,4 @@ resources:
   bugtracker: https://github.com/mark-5/p5-net-zookeeper/issues
   homepage: https://github.com/mark-5/p5-net-zookeeper
   repository: https://github.com/mark-5/p5-net-zookeeper.git
-version: '0.39'
+version: '0.41'
@@ -26,7 +26,7 @@ package Net::ZooKeeper;
 require Exporter;
 require XSLoader;
 
-our $VERSION = '0.39';
+our $VERSION = '0.41';
 
 our @ISA = qw(Exporter);
 
@@ -739,7 +739,7 @@ The following methods are defined for the Net::ZooKeeper class.
 
 Creates a new Net::ZooKeeper handle object and attempts to
 connect to the one of the servers of the given ZooKeeper
-cluster.  As described in the L</Internal POSIX Threads> and
+cluster. On failure, undef will be returned and the error message will be accessible in "$!". As described in the L</Internal POSIX Threads> and
 L</Connection Order> sections, the ZooKeeper client code will
 create an IO thread which maintains the connection with a
 regular "heartbeat" request.  In the event of a connection error
@@ -1144,6 +1144,8 @@ watch object as the value of a C<'watch'> option to a
 Net::ZooKeeper method; methods which accept a C<'watch'> option
 are C<exists()>, C<get_children()>, and C<get()>.
 
+By default, the C<wait()> method will return when session events(such as disconnecting and reconnecting from a server) are triggered. If a watch should ignore these session events, call C<Net::ZooKeeper::set_ignore_session_events(1)>.
+
 When the C<wait()> method is invoked with a C<'timeout'>
 option, it waits for no more than the number of milliseconds
 specified by the C<'timeout'> option.
@@ -1201,6 +1203,12 @@ be randomly reordered prior to connection.
 
 See the L</Connection Order> section for more details.
 
+=item set_ignore_session_events()
+
+  Net::ZooKeeper::set_ignore_session_events(1);
+
+By default, ZooKeeper will trigger all pending watches for any session event(such as disconnecting and reconnecting to a server). If C<Net::ZooKeeper::set_ignore_session_events()> is set to a true value, session events will be ignored and C<wait()> calls will only return after the watch event is triggered or times out.
+
 =back
 
 =head1 EXPORTS
@@ -72,6 +72,7 @@ struct zk_watch_t {
     pthread_mutex_t mutex;
     pthread_cond_t cond;
     int done;
+    int watch_event_triggered;
     int ret;
     int event_type;
     int event_state;
@@ -163,12 +164,22 @@ static zk_key_t zk_watch_keys[NUM_WATCH_KEYS] = {
     {"state", 0, 0, 0, 0}
 };
 
+static int zk_ignore_session_events = 0;
 
 static void _zk_watcher(zhandle_t *handle, int type, int state,
                         const char *path, void *context)
 {
     zk_watch_t *watch_ctx = context;
 
+    bool is_session_event = type == ZOO_SESSION_EVENT;
+    if (is_session_event && zk_ignore_session_events) {
+        return;
+    }
+    if (!is_session_event) {
+        // mark this watch for garbage collection
+        watch_ctx->watch_event_triggered = 1;
+    }
+
     pthread_mutex_lock(&watch_ctx->mutex);
 
     watch_ctx->event_type = type;
@@ -279,7 +290,9 @@ static unsigned int _zk_release_watches(pTHX_ zk_watch_t *first_watch,
 
         if (!final) {
             pthread_mutex_lock(&watch->mutex);
-            done = watch->done;
+            // only release watches if the watch event has been triggered
+            //   otherwise zookeeper may trigger it again
+            done = watch->watch_event_triggered;
             pthread_mutex_unlock(&watch->mutex);
         }
 
@@ -753,6 +766,13 @@ zk_set_deterministic_conn_order(flag)
 
         XSRETURN_EMPTY;
 
+void
+zk_set_ignore_session_events(flag)
+        bool flag
+    PPCODE:
+        zk_ignore_session_events = flag;
+
+        XSRETURN_EMPTY;
 
 void
 zk_new(package, hosts, ...)
@@ -211,17 +211,14 @@ sub create
                                 'acl' => ZOO_OPEN_ACL_UNSAFE);
 }
 
-sub get_first_child
+sub get_children_absolute
 {
     my($self, $path) = @_;
 
     my @child_paths = $self->get_children($path);
 
-    if (@child_paths > 0) {
-        return $path . (($path =~ /\/$/) ? '' : '/') . $child_paths[0];
-    }
-
-    return undef;
+    my $prefix = $path . (($path =~ /\/$/) ? '' : '/');
+    return map {$prefix . $_} @child_paths;
 }
 
 sub stat
@@ -298,9 +295,10 @@ SKIP: {
         skip 'no connection to ZooKeeper', 1 unless
             (defined($path) and $path eq $node_path);
 
-        my $child_path = $sub_zkh->get_first_child($root_path);
-        is($child_path, $node_path,
-           'get_first_child(): retrieved first child with subclassed handle');
+        my @children = $sub_zkh->get_children_absolute($root_path);
+        my $child_exists =!! grep {$_ eq $node_path} @children;
+        ok($child_exists,
+           'get_children_absolute(): retrieved new child with subclassed handle');
     }
 
     my $sub_stat = $sub_zkh->stat();