Skip to content

Commit e083f70

Browse files
committed
Merge pull request #61 from pipermerriam/piper/make-ethtesterclient-work-async
Make ethtester async
2 parents 0943a3b + 531b331 commit e083f70

File tree

2 files changed

+93
-4
lines changed

2 files changed

+93
-4
lines changed

populus/ethtester_client.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
55
https://github.com/ConsenSys/eth-testrpc
66
"""
7+
import time
8+
import Queue
9+
import threading
10+
import uuid
711

812

913
from ethereum import utils as ethereum_utils
@@ -121,10 +125,34 @@ class EthTesterClient(object):
121125
Stand-in replacement for the rpc client that speaks directly to the
122126
`ethereum.tester` facilities.
123127
"""
124-
def __init__(self):
128+
def __init__(self, async=True, async_timeout=10):
125129
self.evm = t.state()
126130
self.evm.mine()
127131

132+
self.is_async = async
133+
self.async_timeout = async_timeout
134+
135+
if self.is_async:
136+
self.request_queue = Queue.Queue()
137+
self.results = {}
138+
139+
self.request_thread = threading.Thread(target=self.process_requests)
140+
self.request_thread.daemon = True
141+
self.request_thread.start()
142+
143+
def process_requests(self):
144+
while True:
145+
id, args, kwargs = self.request_queue.get()
146+
mine = kwargs.pop('_mine', False)
147+
try:
148+
self._send_transaction(*args, **kwargs)
149+
if mine:
150+
self.evm.mine()
151+
response = self.evm.last_tx.hash
152+
except ValueError as e:
153+
response = e
154+
self.results[id] = response
155+
128156
def wait_for_block(self, block_number, max_wait=0):
129157
while self.evm.block.number < block_number:
130158
self.evm.mine()
@@ -177,9 +205,34 @@ def _send_transaction(self, _from=None, to=None, gas=None, gas_price=None,
177205
return self.evm.send(sender=sender, to=to, value=value, evmdata=data)
178206

179207
def send_transaction(self, *args, **kwargs):
180-
self._send_transaction(*args, **kwargs)
181-
self.evm.mine()
182-
return self.evm.last_tx.hash
208+
if self.is_async:
209+
kwargs['_mine'] = True
210+
request_id = uuid.uuid4()
211+
self.request_queue.put((request_id, args, kwargs))
212+
start = time.time()
213+
while time.time() - start < self.async_timeout:
214+
if request_id in self.results:
215+
return self.results.pop(request_id)
216+
raise ValueError("Timeout waiting for {0}".format(request_id))
217+
else:
218+
self._send_transaction(*args, **kwargs)
219+
self.evm.mine()
220+
return self.evm.last_tx.hash
221+
222+
def make_ipc_request(self, *args, **kwargs):
223+
if self.is_async:
224+
request_id = uuid.uuid4()
225+
self.request_queue.put((request_id, args, kwargs))
226+
start = time.time()
227+
while time.time() - start < 10:
228+
if request_id in self.results:
229+
result = self.results.pop(request_id)
230+
if isinstance(result, Exception):
231+
raise result
232+
return result
233+
raise ValueError("Timeout waiting for {0}".format(request_id))
234+
else:
235+
return self._make_ipc_request(*args, **kwargs)
183236

184237
def _get_transaction_by_hash(self, txn_hash):
185238
txn_hash = strip_0x(txn_hash)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
import threading
3+
4+
from ethereum import tester
5+
6+
from populus.ethtester_client import EthTesterClient
7+
8+
9+
def test_async_requests():
10+
client = EthTesterClient(async=True, async_timeout=60)
11+
12+
threads = []
13+
errors = []
14+
to_addr = tester.encode_hex(tester.accounts[1])
15+
16+
def spam_block_number():
17+
for i in range(5):
18+
try:
19+
client.send_transaction(
20+
to=to_addr,
21+
value=1,
22+
)
23+
except Exception as e:
24+
errors.append(e)
25+
pytest.fail(e.message)
26+
27+
for i in range(5):
28+
thread = threading.Thread(target=spam_block_number)
29+
thread.daemon = True
30+
threads.append(thread)
31+
32+
[thread.start() for thread in threads]
33+
34+
[thread.join() for thread in threads]
35+
36+
assert not errors

0 commit comments

Comments
 (0)