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

Commit 10d98da

Browse files
committed
Import subrepo ./:execute at 2941b5fc19e016370765a41d88998d14dc26a052
Import subrepo ./:cleanup at 4dfafa87af5aed90bc6bc60781b376655ec9060d
1 parent cc7e74e commit 10d98da

File tree

2 files changed

+81
-8
lines changed

2 files changed

+81
-8
lines changed

execute/src/deso/execute/execute_.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,19 @@ def _read(data):
192192
_OUT = POLLOUT | POLLHUP | POLLERR
193193
# The event mask for which to poll for a read channel (such as stdout).
194194
_IN = POLLPRI | POLLHUP | POLLIN
195-
# The event mask describing all error events on which we close the
196-
# respective file descriptor.
197-
_ERR = POLLHUP | POLLERR | POLLNVAL
195+
196+
197+
def eventToString(events):
198+
"""Convert an event set to a human readable string."""
199+
errors = {
200+
POLLERR: "ERR",
201+
POLLHUP: "HUP",
202+
POLLIN: "IN",
203+
POLLNVAL: "NVAL",
204+
POLLOUT: "OUT",
205+
POLLPRI: "PRI",
206+
}
207+
return "|".join([v for k, v in errors.items() if k & events])
198208

199209

200210
class _PipelineFileDescriptors:
@@ -294,17 +304,39 @@ def pollRead(data):
294304
if event & POLLOUT:
295305
close = _write(data)
296306
elif event & POLLIN or event & POLLPRI:
297-
close = _read(data)
307+
if event & POLLHUP:
308+
# In case we received a combination of a data-is-available
309+
# and a HUP event we need to make sure that we flush the
310+
# entire pipe buffer before we stop the polling. Otherwise
311+
# we might leave data unread that was successfully sent to
312+
# us.
313+
# Note that from a logical point of view this problem
314+
# occurs only in the receive case. In the write case we
315+
# have full control over the file descriptor ourselves and
316+
# if the remote side closes its part there is no point in
317+
# sending any more data.
318+
while not _read(data):
319+
pass
320+
else:
321+
close = _read(data)
298322

299323
# We explicitly (and early, compared to the defers we
300-
# scheduled previously) close the file descriptor on all error
301-
# events and POLLHUP, or when we received EOF (for reading) or
302-
# run out of data to send (for writing).
303-
if event & _ERR or close:
324+
# scheduled previously) close the file descriptor on POLLHUP,
325+
# when we received EOF (for reading), or run out of data to
326+
# send (for writing).
327+
if event & POLLHUP or close:
304328
data["close"]()
305329
data["unreg"]()
306330
del polls[fd]
307331

332+
# All error codes are reported to clients such that they can
333+
# deal with potentially incomplete data.
334+
if event & (POLLERR | POLLNVAL):
335+
string = eventToString(event)
336+
error = "Error while polling for new data, event: {s} ({e})"
337+
error = error.format(s=string, e=event)
338+
raise ConnectionError(error)
339+
308340
return self._stdout["data"] if self._stdout else b"",\
309341
self._stderr["data"] if self._stderr else b""
310342

execute/src/deso/execute/test/testExecute.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,23 @@
2525
formatPipeline,
2626
pipeline as pipeline_,
2727
)
28+
from deso.execute.execute_ import (
29+
eventToString,
30+
)
2831
from os import (
2932
remove,
3033
)
3134
from os.path import (
3235
isfile,
3336
)
37+
from select import (
38+
POLLERR,
39+
POLLHUP,
40+
POLLIN,
41+
POLLNVAL,
42+
POLLOUT,
43+
POLLPRI,
44+
)
3445
from tempfile import (
3546
mktemp,
3647
)
@@ -65,6 +76,36 @@ def pipeline(commands, data_in=None, read_out=False, read_err=False):
6576

6677
class TestExecute(TestCase):
6778
"""A test case for command execution functionality."""
79+
def testExecuteErrorEventToStringSingle(self):
80+
"""Verify that our event to string conversion works as expected."""
81+
self.assertEqual(eventToString(POLLERR), "ERR")
82+
self.assertEqual(eventToString(POLLHUP), "HUP")
83+
self.assertEqual(eventToString(POLLIN), "IN")
84+
self.assertEqual(eventToString(POLLNVAL), "NVAL")
85+
self.assertEqual(eventToString(POLLOUT), "OUT")
86+
self.assertEqual(eventToString(POLLPRI), "PRI")
87+
88+
89+
def testExecuteErrorEventToStringMultiple(self):
90+
"""Verify that our event to string conversion works as expected."""
91+
# Note that we cannot say for sure what the order of the event codes
92+
# in the string will be, so we have to check for all possible
93+
# outcomes.
94+
s = eventToString(POLLERR | POLLHUP)
95+
self.assertTrue(s == "ERR|HUP" or s == "HUP|ERR", s)
96+
97+
s = eventToString(POLLIN | POLLNVAL)
98+
self.assertTrue(s == "IN|NVAL" or s == "NVAL|IN", s)
99+
100+
s = eventToString(POLLHUP | POLLOUT | POLLERR)
101+
self.assertTrue(s == "HUP|OUT|ERR" or
102+
s == "HUP|ERR|OUT" or
103+
s == "OUT|HUP|ERR" or
104+
s == "OUT|ERR|HUP" or
105+
s == "ERR|HUP|OUT" or
106+
s == "ERR|OUT|HUP", s)
107+
108+
68109
def testExecuteAndNoOutput(self):
69110
"""Test command execution and output retrieval for empty output."""
70111
output, _ = execute(_TRUE, read_out=True)

0 commit comments

Comments
 (0)