Skip to content

Commit 4537bc1

Browse files
tbsuhtrjarry
authored andcommitted
session: add locking support
1 parent 516a0cb commit 4537bc1

File tree

4 files changed

+82
-1
lines changed

4 files changed

+82
-1
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ Supported Features
153153
``sr_edit_batch()``, ``sr_validate()``, ``sr_apply_changes()``,
154154
``sr_discard_changes()``, ``sr_replace_config()``)
155155
- Get data (``sr_get_data()``, ``sr_get_item()``, ``sr_get_items()``)
156+
- Module locking (``sr_*lock*``)
156157

157158
__ https://pypi.org/project/libyang/
158159
.. _async: https://docs.python.org/3/library/asyncio-task.html#coroutine
@@ -163,7 +164,6 @@ Not Yet Supported Features
163164
All other features are not yet supported by sysrepo-python. The most notable
164165
are:
165166

166-
- Module locking (``sr_*lock*``)
167167
- Module management (``sr_*_module_*``)
168168

169169
Contributing

cffi/cdefs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ int sr_session_set_orig_name(sr_session_ctx_t *session, const char *);
101101
int sr_session_get_orig_data(sr_session_ctx_t *session, uint32_t idx, uint32_t *size, const void **data);
102102
int sr_session_push_orig_data(sr_session_ctx_t * session, uint32_t size, const void *data);
103103

104+
int sr_lock(sr_session_ctx_t *session, const char *module_name, uint32_t timeout_ms);
105+
int sr_unlock(sr_session_ctx_t *session, const char *module_name);
106+
104107
typedef enum sr_val_type_e {
105108
SR_UNKNOWN_T,
106109
SR_LIST_T,

sysrepo/session.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,42 @@ def get_datastore(self) -> str:
111111
"""
112112
return datastore_name(lib.sr_session_get_ds(self.cdata))
113113

114+
def lock(self, module_name: str = "", timeout_ms: int = 0) -> None:
115+
"""
116+
Locks the data of the specified module or the whole datastore.
117+
:arg module_name:
118+
Optional name of the module to be locked.
119+
:arg timeout_ms:
120+
Optional timeout in ms for waiting. If 0, no waiting is performed.
121+
"""
122+
module = str2c(module_name) if len(module_name) > 0 else ffi.NULL
123+
check_call(lib.sr_lock, self.cdata, module, timeout_ms)
124+
125+
def unlock(self, module_name: str = "") -> None:
126+
"""
127+
Unlocks the data of the specified module or the whole datastore.
128+
:arg module_name:
129+
Optional name of the module to be locked.
130+
"""
131+
module = str2c(module_name) if len(module_name) > 0 else ffi.NULL
132+
check_call(lib.sr_unlock, self.cdata, module)
133+
134+
@contextmanager
135+
def locked(self, module_name: str = "", timeout_ms: int = 0):
136+
"""
137+
Convenience method which manages un-/locking the data of the specified module
138+
or the whole datastore.
139+
:arg module_name:
140+
Optional name of the module to be locked.
141+
:arg timeout_ms:
142+
Optional timeout in ms for waiting. If 0, no waiting is performed.
143+
"""
144+
try:
145+
self.lock(module_name, timeout_ms)
146+
yield
147+
finally:
148+
self.unlock(module_name)
149+
114150
def switch_datastore(self, datastore: str) -> None:
115151
"""
116152
Change datastore which the session operates on. All subsequent calls will be

tests/test_session.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import logging
55
import os
6+
import threading
7+
import time
68
import types
79
import unittest
810

@@ -162,3 +164,43 @@ def test_get_netconf_id_and_get_user_are_only_available_in_implicit_session(self
162164

163165
with self.assertRaises(sysrepo.SysrepoUnsupportedError):
164166
sess.get_user()
167+
168+
def test_basic_session_lock(self):
169+
# lock whole datastore
170+
with self.conn.start_session("running") as sess:
171+
with sess.locked():
172+
config = {"conf": {"system": {"hostname": "foobar1"}}}
173+
sess.replace_config(config, "sysrepo-example")
174+
sess.apply_changes()
175+
176+
# lock specific module name with a timeout
177+
with self.conn.start_session("running") as sess:
178+
with sess.locked("sysrepo-example", 1000):
179+
config = {"conf": {"system": {"hostname": "foobar2"}}}
180+
sess.replace_config(config, "sysrepo-example")
181+
sess.apply_changes()
182+
183+
def test_concurrent_session_lock(self):
184+
def function_thread_one():
185+
with self.conn.start_session("running") as sess:
186+
with sess.locked():
187+
config = {"conf": {"system": {"hostname": "foobar1"}}}
188+
sess.replace_config(config, "sysrepo-example")
189+
sess.apply_changes()
190+
time.sleep(3) # keep the lock active
191+
192+
def function_thread_two():
193+
time.sleep(1)
194+
with self.conn.start_session("running") as sess:
195+
with self.assertRaises(sysrepo.SysrepoLockedError):
196+
with sess.locked():
197+
pass
198+
199+
thread_one = threading.Thread(target=function_thread_one)
200+
thread_two = threading.Thread(target=function_thread_two)
201+
202+
thread_one.start()
203+
thread_two.start()
204+
205+
thread_one.join()
206+
thread_two.join()

0 commit comments

Comments
 (0)