Skip to content

Commit 9000c82

Browse files
authored
Handle zc.lockfile.LockError in lock_resource (#39)
Handle zc.lockfile.LockError in lock_resource Co-authored-by: StabbarN <[omitted]>
1 parent 5690112 commit 9000c82

File tree

4 files changed

+49
-23
lines changed

4 files changed

+49
-23
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ These people have contributed to `pytest-services`, in alphabetical order:
1212
* `Joep van Dijken <[email protected]>`_
1313
* `Oleg Pidsadnyi <[email protected]>`_
1414
* `Zac Hatfield-Dodds <[email protected]>`_
15+
.. * `Magnus Staberg <[email protected]>`_

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Changelog
22
=========
33

4+
Unreleased
5+
-----
6+
7+
- #38: Retry to lock resource if zc.lockfile.LockError is raised. Fix needed for pytest-xdist. (StabbarN)
8+
49
2.1.0
510
-----
611

README.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ Infrastructure fixtures
115115
* display_getter
116116
Function to get unallocated display.
117117
Automatically ensures locking and un-locking of it on application level via flock.
118-
118+
* lock_resource_timeout
119+
Used in function lock_resource.
120+
A maximum of total sleep between attempts to lock resource.
119121

120122
Service fixtures
121123
****************

pytest_services/locks.py

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import contextlib
33
import json
44
import os
5+
from random import random
56
import socket
7+
import time
68

79
import pytest
810
import zc.lockfile
@@ -80,23 +82,39 @@ def unlock_display(display, lock_dir, services_log):
8082
return unlock_resource('display', display, lock_dir, services_log)
8183

8284

83-
def lock_resource(name, resource_getter, lock_dir, services_log):
84-
"""Issue a lock for given resource."""
85-
with locked_resources(name, lock_dir) as bound_resources:
86-
services_log.debug('bound_resources {0}: {1}'.format(name, bound_resources))
87-
88-
resource = resource_getter(bound_resources)
89-
while resource in bound_resources:
90-
# resource is already taken by someone, retry
91-
services_log.debug('bound resources {0}: {1}'.format(name, bound_resources))
92-
resource = resource_getter(bound_resources)
93-
services_log.debug('free resource choosen {0}: {1}'.format(name, resource))
94-
bound_resources.append(resource)
95-
services_log.debug('bound resources {0}: {1}'.format(name, bound_resources))
96-
return resource
85+
@pytest.fixture(scope='session')
86+
def lock_resource_timeout():
87+
"""Max number of seconds to obtain the lock."""
88+
return 20
9789

9890

99-
def get_free_port(lock_dir, services_log):
91+
def lock_resource(name, resource_getter, lock_dir, services_log, lock_resource_timeout):
92+
"""Issue a lock for given resource."""
93+
total_seconds_slept = 0
94+
while True:
95+
try:
96+
with locked_resources(name, lock_dir) as bound_resources:
97+
services_log.debug('bound_resources {0}: {1}'.format(name, bound_resources))
98+
resource = resource_getter(bound_resources)
99+
while resource in bound_resources:
100+
# resource is already taken by someone, retry
101+
services_log.debug('bound resources {0}: {1}'.format(name, bound_resources))
102+
resource = resource_getter(bound_resources)
103+
services_log.debug('free resource choosen {0}: {1}'.format(name, resource))
104+
bound_resources.append(resource)
105+
services_log.debug('bound resources {0}: {1}'.format(name, bound_resources))
106+
return resource
107+
except zc.lockfile.LockError as err:
108+
if total_seconds_slept >= lock_resource_timeout:
109+
raise err
110+
services_log.debug('lock resource failed: {0}'.format(err))
111+
112+
seconds_to_sleep = random() * 0.1 + 0.05
113+
total_seconds_slept += seconds_to_sleep
114+
time.sleep(seconds_to_sleep)
115+
116+
117+
def get_free_port(lock_dir, services_log, lock_resource_timeout):
100118
"""Get free port to listen on."""
101119
def get_port(bound_resources):
102120
if bound_resources:
@@ -114,10 +132,10 @@ def get_port(bound_resources):
114132
pass
115133
port += 1
116134

117-
return lock_resource('port', get_port, lock_dir, services_log)
135+
return lock_resource('port', get_port, lock_dir, services_log, lock_resource_timeout)
118136

119137

120-
def get_free_display(lock_dir, services_log):
138+
def get_free_display(lock_dir, services_log, lock_resource_timeout):
121139
"""Get free display to listen on."""
122140
def get_display(bound_resources):
123141
display = 100
@@ -129,15 +147,15 @@ def get_display(bound_resources):
129147
continue
130148
return display
131149

132-
return lock_resource('display', get_display, lock_dir, services_log)
150+
return lock_resource('display', get_display, lock_dir, services_log, lock_resource_timeout)
133151

134152

135153
@pytest.fixture(scope='session')
136-
def port_getter(request, lock_dir, services_log):
154+
def port_getter(request, lock_dir, services_log, lock_resource_timeout):
137155
"""Lock getter function."""
138156
def get_port():
139157
"""Lock a free port and unlock it on finalizer."""
140-
port = get_free_port(lock_dir, services_log)
158+
port = get_free_port(lock_dir, services_log, lock_resource_timeout)
141159

142160
def finalize():
143161
unlock_port(port, lock_dir, services_log)
@@ -147,11 +165,11 @@ def finalize():
147165

148166

149167
@pytest.fixture(scope='session')
150-
def display_getter(request, lock_dir, services_log):
168+
def display_getter(request, lock_dir, services_log, lock_resource_timeout):
151169
"""Display getter function."""
152170
def get_display():
153171
"""Lock a free display and unlock it on finalizer."""
154-
display = get_free_display(lock_dir, services_log)
172+
display = get_free_display(lock_dir, services_log, lock_resource_timeout)
155173
request.addfinalizer(lambda: unlock_display(display, lock_dir, services_log))
156174
return display
157175
return get_display

0 commit comments

Comments
 (0)