Skip to content

Commit

Permalink
fix: use separate transaction storage for each DictProxy handler
Browse files Browse the repository at this point in the history
DictProxy can have transactions with the same name
(most frequently `1`) processed in parallel.
Dovecot expects that transaction names on each connection
are independent.
  • Loading branch information
link2xt committed Jul 31, 2024
1 parent a9bdc3d commit 9e3df0a
Showing 1 changed file with 14 additions and 16 deletions.
30 changes: 14 additions & 16 deletions chatmaild/src/chatmaild/dictproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@


class DictProxy:
def __init__(self):
self.transactions = {}

def loop_forever(self, rfile, wfile):
def loop_forever(self, rfile, wfile, transactions):
while True:
msg = rfile.readline().strip().decode()
if not msg:
break

res = self.handle_dovecot_request(msg)
res = self.handle_dovecot_request(msg, transactions)
if res:
wfile.write(res.encode("ascii"))
wfile.flush()

def handle_dovecot_request(self, msg):
def handle_dovecot_request(self, msg, transactions):
# see https://doc.dovecot.org/developer_manual/design/dict_protocol/#dovecot-dict-protocol
short_command = msg[0]
parts = msg[1:].split("\t")
Expand All @@ -37,11 +34,11 @@ def handle_dovecot_request(self, msg):
transaction_id = parts[0]

if short_command == "B":
return self.handle_begin_transaction(transaction_id, parts)
return self.handle_begin_transaction(transaction_id, parts, transactions)
elif short_command == "C":
return self.handle_commit_transaction(transaction_id, parts)
return self.handle_commit_transaction(transaction_id, parts, transactions)
elif short_command == "S":
return self.handle_set(transaction_id, parts)
return self.handle_set(transaction_id, parts, transactions)

def handle_lookup(self, parts):
logging.warning(f"lookup ignored: {parts!r}")
Expand All @@ -52,27 +49,28 @@ def handle_iterate(self, parts):
# If we don't return empty line Dovecot will timeout.
return "\n"

def handle_begin_transaction(self, transaction_id, parts):
def handle_begin_transaction(self, transaction_id, parts, transactions):
addr = parts[1]
self.transactions[transaction_id] = dict(addr=addr, res="O\n")
transactions[transaction_id] = dict(addr=addr, res="O\n")

def handle_set(self, transaction_id, parts):
def handle_set(self, transaction_id, parts, transactions):
# For documentation on key structure see
# https://github.com/dovecot/core/blob/main/src/lib-storage/mailbox-attribute.h

self.transactions[transaction_id]["res"] = "F\n"
transactions[transaction_id]["res"] = "F\n"

def handle_commit_transaction(self, transaction_id, parts):
def handle_commit_transaction(self, transaction_id, parts, transactions):
# return whatever "set" command(s) set as result.
return self.transactions.pop(transaction_id)["res"]
transactions.pop(transaction_id)["res"]

def serve_forever_from_socket(self, socket):
dictproxy = self

class Handler(StreamRequestHandler):
def handle(self):
transactions = {}
try:
dictproxy.loop_forever(self.rfile, self.wfile)
dictproxy.loop_forever(self.rfile, self.wfile, transactions)
except Exception:
logging.exception("Exception in the handler")
raise
Expand Down

0 comments on commit 9e3df0a

Please sign in to comment.