Skip to content
This repository was archived by the owner on Jun 15, 2025. It is now read-only.

Commit 661d397

Browse files
committed
Import subrepo ./:execute at 2233b2acfab721cf9412305b0c65f7b27c86cac0
Import subrepo ./:cleanup at 30129de755f5f7a5b9647b792ca1373b94e15dcb
1 parent 94ea899 commit 661d397

File tree

4 files changed

+92
-21
lines changed

4 files changed

+92
-21
lines changed

cleanup/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,40 @@ This example also illustrates another important fact: execution of the various
2727
cleanup routines happens in reverse order of their registration. This property
2828
is important in most scenarios where resources of interest have dependencies.
2929

30+
Sometimes cleanup is only necessary in case an error occurs. That is, if
31+
all operations (resource acquisitions etc.) succeed, we do not want to
32+
roll back and undo a part of them. To that end, a defer context can be
33+
"released" in which case no cleanup happens after block exit. Revisiting
34+
the example above:
35+
36+
```python
37+
client = Client()
38+
with defer() as d:
39+
obj = Object()
40+
d.defer(obj.destroy)
41+
obj.register(client)
42+
d.defer(lambda: obj.unregister(client))
43+
44+
# Do some action that potentially raises an error.
45+
46+
# If we got here we want to keep the object created and registered
47+
# with the client.
48+
d.release()
49+
```
50+
51+
This mechanism not only works on the level of a context but also for
52+
individually deferred functions:
53+
```python
54+
client = Client()
55+
with defer() as d:
56+
obj = Object()
57+
f = d.defer(obj.destroy)
58+
59+
# Do some action that potentially raises an error.
60+
61+
f.release()
62+
```
63+
3064

3165
Installation
3266
------------

cleanup/src/deso/cleanup/defer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ def __call__(self):
4949
# Mark function as executed.
5050
self._function = None
5151

52+
def release(self):
53+
"""Release the function from the defer operation without invoking it."""
54+
self._function = None
55+
5256
class _Defer:
5357
"""Objects of this class act as a context with which to register deferred functions."""
5458
def __init__(self):
@@ -69,6 +73,10 @@ def defer(self, function):
6973
self._functions += [result]
7074
return result
7175

76+
def release(self):
77+
"""Release all deferred functions without executing them."""
78+
self._functions = []
79+
7280
def destroy(self):
7381
"""Destroy the object, invoke all deferred functions."""
7482
# Always invoke in reverse order of registration.

cleanup/src/deso/cleanup/test/testDefer.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,29 @@ def testDeferRunEarlyRunOnce(self):
120120
self.assertEqual(self._counter.count(), 1)
121121

122122

123+
def testDeferFunctionRelease(self):
124+
"""Test the release functionality of deferred functions."""
125+
with defer() as d:
126+
f = d.defer(self._counter.increment)
127+
_ = d.defer(self._counter.increment)
128+
129+
f.release()
130+
131+
# Only one increment should have been executed.
132+
self.assertEqual(self._counter.count(), 1)
133+
134+
135+
def testDeferReleaseAll(self):
136+
"""Test the release functionality of defer objects."""
137+
with defer() as d:
138+
d.defer(self._counter.increment)
139+
d.defer(self._counter.increment)
140+
141+
d.release()
142+
143+
# No function should have been executed.
144+
self.assertEqual(self._counter.count(), 0)
145+
146+
123147
if __name__ == "__main__":
124148
main()

execute/src/deso/execute/execute_.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,25 @@ def pipeRead(argument, data):
236236
self._stdout = {}
237237
self._stderr = {}
238238

239+
# We want to redirect all file descriptors that we do not want
240+
# anything from to /dev/null. But we only want to open the latter
241+
# in case someone really requires it, i.e., if not all three
242+
# channels are connected to pipes or user-defined file descriptors
243+
# anyway.
244+
if stdin is None or stdout is None or stderr is None:
245+
null = open_(devnull, O_RDWR | O_CLOEXEC)
246+
here.defer(lambda: close_(null))
247+
248+
if stdin is None:
249+
stdin = null
250+
if stdout is None:
251+
stdout = null
252+
if stderr is None:
253+
stderr = null
254+
255+
# At this point stdin, stdout, and stderr are all either a valid
256+
# file descriptor (i.e., of type int) or some data.
257+
239258
# Now, depending on whether we got passed in a file descriptor (an
240259
# object of type int), remember it or create a pipe to read or write
241260
# data.
@@ -334,8 +353,7 @@ def pollRead(data):
334353
error = error.format(s=string, e=event)
335354
raise ConnectionError(error)
336355

337-
return self._stdout["data"] if self._stdout else b"",\
338-
self._stderr["data"] if self._stderr else b""
356+
return self.data()
339357

340358

341359
def stdin(self):
@@ -353,6 +371,12 @@ def stderr(self):
353371
return self._stderr["out"] if self._stderr else self._file_err
354372

355373

374+
def data(self):
375+
"""Retrieve the data polled so far as a (stdout, stderr) tuple."""
376+
return self._stdout["data"] if self._stdout else b"",\
377+
self._stderr["data"] if self._stderr else b""
378+
379+
356380
def pipeline(commands, stdin=None, stdout=None, stderr=b""):
357381
"""Execute a pipeline, supplying the given data to stdin and reading from stdout & stderr.
358382
@@ -368,25 +392,6 @@ def pipeline(commands, stdin=None, stdout=None, stderr=b""):
368392
"""
369393
with defer() as later:
370394
with defer() as here:
371-
# We want to redirect all file descriptors that we do not want
372-
# anything from to /dev/null. But we only want to open the latter
373-
# in case someone really requires it, i.e., if not all three
374-
# channels are connected to pipes or user-defined file descriptors
375-
# anyway.
376-
if stdin is None or stdout is None or stderr is None:
377-
null = open_(devnull, O_RDWR | O_CLOEXEC)
378-
here.defer(lambda: close_(null))
379-
380-
if stdin is None:
381-
stdin = null
382-
if stdout is None:
383-
stdout = null
384-
if stderr is None:
385-
stderr = null
386-
387-
# At this point stdin, stdout, and stderr are all either a valid
388-
# file descriptor (i.e., of type int) or some data.
389-
390395
# Set up the file descriptors to pass to our execution pipeline.
391396
fds = _PipelineFileDescriptors(later, here, stdin, stdout, stderr)
392397

0 commit comments

Comments
 (0)