Skip to content

Commit 897caf2

Browse files
committed
Fix race condition in command handling with r2pipe-py-sync
1 parent 616d8be commit 897caf2

File tree

2 files changed

+63
-36
lines changed

2 files changed

+63
-36
lines changed

python/python-wrapper

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

55
PCS="${PYTHON_CONFIG}
6+
python3.12
7+
python3.11
68
python3
79
python39
810
python3.9

python/r2pipe/open_sync.py

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -121,45 +121,70 @@ def __make_non_blocking(fd):
121121
return res != 0
122122

123123
def _cmd_process(self, cmd):
124-
cmd = cmd.strip().replace("\n", ";")
125-
try:
126-
self.process.stdin.write((cmd + "\n").encode("utf8"))
127-
except:
128-
return ''
129-
r = self.process.stdout
130-
self.process.stdin.flush()
131-
out = bytearray()
132-
foo = None
133-
while True:
134-
if self.process.poll() is not None:
135-
raise RuntimeError(f"Process terminated unexpectedly trying to run the command {cmd}\n{self.process}")
124+
# Add a simple mutex-like lock mechanism using threading
125+
import threading
126+
if not hasattr(self, '_cmd_lock'):
127+
self._cmd_lock = threading.Lock()
128+
129+
# Acquire lock to ensure commands don't interfere with each other
130+
with self._cmd_lock:
131+
# Ensure pending buffer is cleared before starting a new command to avoid mixing
132+
old_pending = self.pending
133+
self.pending = b""
134+
135+
cmd = cmd.strip().replace("\n", ";")
136136
try:
137-
null_start = False
138-
if len(self.pending) > 0:
139-
foo = self.pending
140-
self.pending = b""
141-
else:
142-
foo = r.read(4096)
143-
if os.name == "nt":
144-
if foo.startswith(b"\x00"):
145-
foo = foo[1:]
146-
null_start = True
147-
if foo:
148-
zro = foo.find(b"\x00")
149-
if zro != -1:
150-
out += foo[0:zro]
151-
if zro < len(foo):
152-
self.pending = foo[zro + 1:]
137+
self.process.stdin.write((cmd + "\n").encode("utf8"))
138+
except:
139+
self.pending = old_pending # Restore pending on failure
140+
return ''
141+
142+
r = self.process.stdout
143+
self.process.stdin.flush()
144+
out = bytearray()
145+
foo = None
146+
147+
# First read any pending data from previous commands if exists
148+
if old_pending:
149+
zro = old_pending.find(b"\x00")
150+
if zro != -1:
151+
# This is a complete response from previous command, process it first
152+
out += old_pending[0:zro]
153+
if zro + 1 < len(old_pending):
154+
self.pending = old_pending[zro + 1:]
155+
return out.decode("utf-8", errors="ignore")
156+
157+
# Main read loop for current command
158+
while True:
159+
if self.process.poll() is not None:
160+
raise RuntimeError(f"Process terminated unexpectedly trying to run the command {cmd}\n{self.process}")
161+
try:
162+
null_start = False
163+
if len(self.pending) > 0:
164+
foo = self.pending
165+
self.pending = b""
166+
else:
167+
foo = r.read(4096)
168+
if os.name == "nt":
169+
if foo.startswith(b"\x00"):
170+
foo = foo[1:]
171+
null_start = True
172+
if foo:
173+
zro = foo.find(b"\x00")
174+
if zro != -1:
175+
out += foo[0:zro]
176+
if zro + 1 < len(foo):
177+
self.pending = foo[zro + 1:]
178+
break
179+
out += foo
180+
elif null_start:
153181
break
154-
out += foo
155-
elif null_start:
156-
break
157182

158-
except KeyboardInterrupt as e:
159-
raise e
160-
except:
161-
pass
162-
return out.decode("utf-8", errors="ignore")
183+
except KeyboardInterrupt as e:
184+
raise e
185+
except:
186+
pass
187+
return out.decode("utf-8", errors="ignore")
163188

164189
def _cmd_http(self, cmd):
165190
try:

0 commit comments

Comments
 (0)