Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,20 @@ def set_msg_id(self, msg):
self.msg_id = '#%s ' % test
return (' '.join(msg.split(' ')[1:]))

def with_msg_id(self, callback):
# a reply sent after the handler returns, from a DB callback or the login queue, still
# answers the command that started it. Capture that command's id now and put it back
# on the client only while the callback runs.
msg_id = self.msg_id
def run(*args, **kwargs):
previous = self.msg_id
self.msg_id = msg_id
try:
return callback(*args, **kwargs)
finally:
self.msg_id = previous
return run

def setFlagByIP(self, ip, force=True):
cc = ip2country.lookup(ip)
if force or cc != '??':
Expand Down
6 changes: 3 additions & 3 deletions DataHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def __init__(self):
# simultaneously backed up on their write buffers (the 2.3 producer signal), new
# logins are queued FIFO and drained by drain_login_queue() (a 1s LoopingCall) once
# the backpressure clears. Under normal load the queue stays empty.
self.login_queue = collections.deque() # (client, login_args) awaiting login under backpressure
self.login_queue = collections.deque() # (client, login_now carrying the LOGIN's msg_id, login_args) awaiting login under backpressure
self.login_backpressure_limit = 50 # paused-producer count above which login admission pauses

# rate limits
Expand Down Expand Up @@ -807,11 +807,11 @@ def drain_login_queue(self):
# shares login_queue with in_LOGIN without locking. Each login gets its own session
# commit/rollback/close, mirroring the per-request guards in dataReceived.
while self.login_queue and not self.login_backpressured():
client, args = self.login_queue.popleft()
client, login_now, args = self.login_queue.popleft()
if client.session_id not in self.clients:
continue # client disconnected while queued
try:
self.protocol.login_now(client, *args)
login_now(client, *args)
self.session_manager.commit_guard()
except:
logging.error(traceback.format_exc())
Expand Down
10 changes: 7 additions & 3 deletions PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,13 @@ cannot drift. **[GAP]** the `@emits` convention does not exist yet — see
argument may legitimately contain spaces (e.g. chat text, topics). This is the
"sentence argument" behaviour. (`Protocol.py` `get_function_args`.)
- **Message IDs.** A client may prefix a command with `#<id> ` to correlate a request
with the server's reply; the server echoes the id back on responses generated in that
command's handling. (`Client.py`.) **[GAP]** document exact echo semantics and which
responses carry the id vs which do not.
with the server's reply. The server puts the same `#<id> ` on every line it sends that
client as the answer to that command, including refusals (`SERVERMSG`, `FAILED`) and
replies that arrive later because the command waited on the database or the login queue.
Lines that are not an answer to the command carry no id, even when they arrive before
the answer: chat from other players, broadcasts, status changes, and offline messages
delivered after `LOGIN`. Lines sent to other clients as a result of the command carry no
id either. (`Client.py` `set_msg_id`, `with_msg_id`.)
- **Tab-separated payloads.** Some structured fields (notably battle `script_tags`) use
tab-separated `key=value` pairs. **[GAP]** enumerate every command that uses tab
separation and the exact field grammar.
Expand Down
Loading
Loading