-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #155 from SvenskaSpel/allow-concurrent-distributors
Allow concurrent distributors
- Loading branch information
Showing
5 changed files
with
76 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
asdf1 | ||
qwerty2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,59 @@ | ||
from typing import Dict, Iterator, Optional | ||
import logging | ||
from gevent.event import AsyncResult | ||
from locust import User | ||
|
||
import greenlet | ||
from locust.env import Environment | ||
from locust.runners import WorkerRunner | ||
|
||
_results: Dict[int, AsyncResult] = {} | ||
_iterator: Optional[Iterator[Dict]] = None | ||
|
||
|
||
# received on master | ||
def _distributor_request(environment: Environment, msg, **kwargs): | ||
data = next(_iterator) | ||
environment.runner.send_message( | ||
"distributor_response", | ||
{"payload": data, "user_id": msg.data["user_id"]}, | ||
client_id=msg.data["client_id"], | ||
) | ||
|
||
|
||
# received on worker | ||
def _distributor_response(environment: Environment, msg, **kwargs): | ||
_results[msg.data["user_id"]].set(msg.data) | ||
|
||
|
||
def register(environment: Environment, iterator: Optional[Iterator[dict]]): | ||
"""Register distributor method handlers and tie them to use the iterator that you pass. | ||
iterator is not used on workers, so you can leave it as None there. | ||
""" | ||
global _iterator | ||
_iterator = iterator | ||
|
||
runner = environment.runner | ||
assert iterator or isinstance(runner, WorkerRunner), "iterator is a mandatory parameter when not on a worker runner" | ||
if runner: | ||
runner.register_message("distributor_request", _distributor_request) | ||
runner.register_message("distributor_response", _distributor_response) | ||
|
||
|
||
def getdata(user: User) -> Dict: | ||
"""Get the next data dict from iterator | ||
Args: | ||
user (User): current user object (we use the object id of the User to keep track of who's waiting for which data) | ||
""" | ||
if not user.environment.runner: # no need to do anything clever if there is no runner | ||
assert _iterator, "Did you forget to call register() before trying to get data?" | ||
return next(_iterator) | ||
|
||
if id(user) in _results: | ||
logging.warning("This user was already waiting for data. Strange.") | ||
|
||
_results[id(user)] = AsyncResult() | ||
runner = user.environment.runner | ||
runner.send_message("distributor_request", {"user_id": id(user), "client_id": runner.client_id}) | ||
data = _results[id(user)].get()["payload"] # this waits for the reply | ||
del _results[id(user)] | ||
return data | ||
class Distributor: | ||
def __init__(self, environment: Environment, iterator: Optional[Iterator], name="distributor"): | ||
"""Register distributor method handlers and tie them to use the iterator that you pass. | ||
iterator is not used on workers, so you can leave it as None there. | ||
""" | ||
self.iterator = iterator | ||
self.name = name | ||
self.runner = environment.runner | ||
assert iterator or isinstance( | ||
self.runner, WorkerRunner | ||
), "iterator is a mandatory parameter when not on a worker runner" | ||
if self.runner: | ||
# received on master | ||
def _distributor_request(environment: Environment, msg, **kwargs): | ||
data = next(self.iterator) | ||
self.runner.send_message( | ||
f"_{name}_response", | ||
{"payload": data, "user_id": msg.data["user_id"]}, | ||
client_id=msg.data["client_id"], | ||
) | ||
|
||
# received on worker | ||
def _distributor_response(environment: Environment, msg, **kwargs): | ||
_results[msg.data["user_id"]].set(msg.data) | ||
|
||
self.runner.register_message(f"_{name}_request", _distributor_request) | ||
self.runner.register_message(f"_{name}_response", _distributor_response) | ||
|
||
def __next__(self): | ||
"""Get the next data dict from iterator | ||
Args: | ||
user (User): current user object (we use the object id of the User to keep track of who's waiting for which data) | ||
""" | ||
if not self.runner: # no need to do anything clever if there is no runner | ||
assert self.iterator | ||
return next(self.iterator) | ||
|
||
gid = greenlet.getcurrent().minimal_ident # type: ignore | ||
|
||
if gid in _results: | ||
logging.warning("This user was already waiting for data. Strange.") | ||
|
||
_results[gid] = AsyncResult() | ||
self.runner.send_message(f"_{self.name}_request", {"user_id": gid, "client_id": self.runner.client_id}) | ||
data = _results[gid].get()["payload"] # this waits for the reply | ||
del _results[gid] | ||
return data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters