Skip to content

Commit

Permalink
Prevent multiple duplicate callback executions.
Browse files Browse the repository at this point in the history
If the Watcher.get() data is the same as the last time the
Watcher._execute_callbacks() method was called, theres no
reason to re-execute the callbacks ... so don't.
  • Loading branch information
diranged committed Jun 3, 2014
1 parent 05faeb7 commit 0613395
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 2 deletions.
20 changes: 18 additions & 2 deletions nd_service_registry/watcher.py
Expand Up @@ -99,6 +99,10 @@ def __init__(self, zk, path, callback=None, watch_children=True,
if callback:
self._callbacks.append(callback)

# Quick copy of the data last used when executing callbacks -- used
# to prevent duplicate callback executions due to multiple watches.
self._last_callback_executed_with = None

# if self._state is False, then even on a data change, our callbacks
# do not run.
self._state = True
Expand Down Expand Up @@ -221,9 +225,17 @@ def _execute_callbacks(self):
Args:
path: A string value of the 'path' that has been updated. This
triggers the callbacks registered for that path only."""
log.debug('[%s] execute_callbacks triggered' % self._path)
triggers the callbacks registered for that path only.
"""

# If the current data and the last-execution data are the same, then
# assume our callback notification was bogus and don't run.
if self._last_callback_executed_with == self.get():
log.debug('[%s] Last callback data matches current data, not '
'executing callbacks again.' % self._path)
return

log.debug('[%s] execute_callbacks triggered' % self._path)
if not self.state():
log.debug('[%s] self.state() is False - not executing callbacks.'
% self._path)
Expand All @@ -233,6 +245,10 @@ def _execute_callbacks(self):
log.debug('[%s] Executing callback %s' % (self._path, callback))
callback(self.get())

# Store a "last called with" variable that we can check to prevent
# unnecessary extra callback executions.
self._last_callback_executed_with = self.get()


class DummyWatcher(Watcher):
"""Provides a Watcher-interface, without any actual Zookeeper connection.
Expand Down
12 changes: 12 additions & 0 deletions nd_service_registry/watcher_tests.py
Expand Up @@ -35,6 +35,18 @@ def test_init(self):
self.assertTrue(self.callback_watcher.test in self.watch._callbacks)
self.assertEquals(self.watch._data_watch, self.data_watch.return_value)

def test_execute_callbacks(self):
# Reset any knowledge of calls to the callback from the setUp()
self.callback_watcher.reset_mock()

# Execute the callback method several times. It sohuld only fire off
# once
for i in xrange(0, 5):
self.watch._execute_callbacks()

# Executing the callbacks should happen only when the data changes.
self.assertEquals(1, self.callback_watcher.test.call_count)

def test_update_with_stat_and_data(self):
fake_data_str = 'unittest'
expected_decoded_fake_data = {'string_value': 'unittest'}
Expand Down

0 comments on commit 0613395

Please sign in to comment.