Browse files

first commit

  • Loading branch information...
0 parents commit 96863b5572ddb25bfd4900010c3b460a8167ee92 Satoshi Tanimoto committed Jan 15, 2012
Showing with 275 additions and 0 deletions.
  1. +5 −0 .gitignore
  2. +5 −0 README.rst
  3. +162 −0 ddlockclient/__init__.py
  4. +14 −0 setup.py
  5. 0 tests/__init__.py
  6. +89 −0 tests/test_client.py
5 .gitignore
@@ -0,0 +1,5 @@
+*.pyc
+/DDLockClient.egg-info
+/build
+/dist
+/env
5 README.rst
@@ -0,0 +1,5 @@
+This is a port of DDLockClient to Python.
+
+DDLockClient is a client library for ddlockd (Danga distributed lock daemon)
+
+See http://code.livejournal.org/trac/ddlockd for more about ddlockd.
162 ddlockclient/__init__.py
@@ -0,0 +1,162 @@
+__version__ = '0.1.0'
+
+import socket
+import re
+import time
+
+DEFAULT_PORT = 7002
+
+
+def eurl_repl(m):
+ return "%%%02X" % ord(m)
+
+
+def eurl(name):
+ name = re.sub(r'([^a-zA-Z0-9_,.\\: -])', eurl_repl, name)
+ name = re.sub(' ', '+', name)
+ return name
+
+
+class DDLockError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return repr(self.msg)
+
+
+class DDLock():
+ def __init__(self, client, name, servers=[]):
+ self.client = client
+ self.name = name
+ self.sockets = self.getlocks(servers)
+
+ def getlocks(self, servers):
+ addrs = []
+
+ def fail(msg):
+ for addr in addrs:
+ sock = self.client.get_sock(addr)
+ if not sock:
+ continue
+ sock.send("releaselock lock=%s\r\n" % eurl(self.name))
+ raise DDLockError(msg)
+
+ for server in servers:
+ host_port = server.split(':')
+ host = host_port[0]
+ port = int(host_port[1]) if len(host_port) > 1 else DEFAULT_PORT
+ addr = "%s:%s" % (host, port)
+
+ sock = self.client.get_sock(addr)
+ if not sock:
+ continue
+
+ sock.send("trylock lock=%s\r\n" % eurl(self.name))
+ data = sock.recv(1024)
+
+ if not re.search(r'^ok\b', data, re.I):
+ fail("%s: '%s' %s\n" % (server, self.name, repr(data)))
+
+ addrs.append(addr)
+
+ if len(addrs) == 0:
+ raise DDLockError("No available lock hosts")
+
+ return addrs
+
+ def release(self):
+ count = 0
+ for addr in self.sockets:
+ sock = self.client.get_sock_onlycache(addr)
+ if not sock:
+ continue
+ data = None
+ try:
+ sock.send("releaselock lock=%s\r\n" % eurl(self.name))
+ data = sock.recv(1024)
+ except:
+ pass
+ if data and not re.search(r'^ok\b', data, re.I):
+ raise DDLockError("releaselock (%s): %s" % (sock.getpeername(),
+ repr(data)))
+ count += 1
+
+ return count
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, val, tb):
+ try:
+ self.release()
+ except:
+ pass
+
+ def __del__(self):
+ try:
+ self.release()
+ except:
+ pass
+
+
+class DDLockClient():
+ servers = []
+ sockcache = {}
+ errmsg = ""
+
+ def __init__(self, servers=[]):
+ self.servers = servers
+
+ def get_sock_onlycache(self, addr):
+ return self.sockcache.get(addr)
+
+ def get_sock(self, addr):
+ host_port = addr.split(':')
+ host = host_port[0]
+ port = int(host_port[1]) if len(host_port) > 1 else DEFAULT_PORT
+
+ sock = self.sockcache.get("%s:%s" % (host, port))
+ if sock and sock.getpeername():
+ return sock
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setblocking(1)
+ sock.connect((host, port))
+
+ self.sockcache[addr] = sock
+ return sock
+
+ def trylock(self, name, timeout=None):
@yannk
yannk added a note Jan 18, 2012

It's interesting you've added the timeout option that we only had at the layer above.

I think it makes sense to expose those rety/multi locks in the lower api to avoid replicating the logic someone else might need. Maybe we should also change the Perl client, to keep them in sync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ return self._trylock_guranteed(name, timeout)
+
+ def _trylock(self, name):
+ lock = None
+ try:
+ lock = DDLock(self, name, self.servers)
+ except DDLockError, e:
+ self.errmsg = str(e)
+ except Exception, e:
+ self.errmsg = "Unknown failure"
+
+ return lock
+
+ def _trylock_guranteed(self, name, timeout=None):
+ lock = None
+ try_until = time.time()
+ if timeout is not None:
+ try_until += timeout
+
+ while not lock:
+ lock = self._trylock(name)
+ if lock:
+ break
+ if timeout is not None and time.time() > try_until:
+ break
+ time.sleep(0.1)
+
+ return lock
+
+ def last_error(self):
+ return self.errmsg
14 setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+from ddlockclient import __version__ as version
+
+setup(
+ name = 'DDLockClient',
+ version = version,
+ author = 'Satoshi Tanimoto',
+ description = 'Python client library for the Danga distributed lock daemon',
+ long_description = open('README.rst').read(),
+ url = 'http://github.com/stanimoto/python-ddlockclient',
+ packages = ['ddlockclient'],
+ license='Apache',
+)
0 tests/__init__.py
No changes.
89 tests/test_client.py
@@ -0,0 +1,89 @@
+import unittest
+import re
+from ddlockclient import DDLockClient, DDLock
+
+servers = ['localhost']
+
+
+class TestError(Exception):
+ pass
+
+
+class ClientTest(unittest.TestCase):
+
+ def setUp(self):
+ self.c = DDLockClient(servers=servers)
+
+ def _lock(self, name):
+ return self.c.trylock(name, 0) # no block
+
+ def test_init(self):
+ self.assertTrue(isinstance(self.c, DDLockClient),
+ "Got a client object")
+
+ def test_a(self):
+ lock = self._lock('test_a')
+ self.assertTrue(isinstance(lock, DDLock),
+ "Got a lock for 'test_a'")
+
+ def test_a2(self):
+ lock = self._lock('test_a')
+ self.assertTrue(isinstance(lock, DDLock),
+ "Got a lock for 'test_a' again")
+
+ def test_b(self):
+ lock = self._lock('test_b')
+ self.assertTrue(isinstance(lock, DDLock),
+ "Got a lock for 'test_b'")
+ rv = lock.release()
+ self.assertTrue(rv, "Lock release succeeded")
+
+ rv = None
+ try:
+ rv = lock.release()
+ self.fail("Expected an error")
+ except Exception, e:
+ self.assertTrue(re.search('ERR didnthave',
+ str(e)),
+ "release() die if it couldn't release")
+ self.assertEquals(rv, None, "no return value")
+
+ lock2 = self._lock('test_b')
+ self.assertTrue(isinstance(lock2, DDLock),
+ "Got a lock for 'test_b' again")
+
+ def test_c(self):
+ lock = self._lock('test_c')
+ self.assertTrue(isinstance(lock, DDLock),
+ "Got a lock for 'test_c'")
+ lock2 = self._lock('test_c')
+ self.assertEquals(lock2,
+ None,
+ "Got no lock for 'test_c' again without release")
+
+ def test_d(self):
+ lock = self._lock('test_d')
+ self.assertTrue(isinstance(lock, DDLock),
+ "Got a lock for 'test_d'")
+ # a lock will be released when going out of with statement
+ with lock:
+ pass
+
+ lock2 = self._lock('test_d')
+ self.assertTrue(isinstance(lock2, DDLock),
+ "Got a lock for 'test_d' again")
+
+ def test_e(self):
+ lock = self._lock('test')
+ self.assertTrue(isinstance(lock, DDLock),
+ "Got a lock for 'test'")
+ try:
+ raise TestError("test error")
+ except Exception, e:
+ self.assertTrue(isinstance(e, TestError))
+ finally:
+ lock.release()
+
+ lock = self._lock('test')
+ self.assertTrue(isinstance(lock, DDLock),
+ "able to lock 'test' again")

0 comments on commit 96863b5

Please sign in to comment.