rewrite pruning, add full tests
Apparently it was broken: the first time the LoopingCall fired, it would throw an exception, and never try again. Now it should be fixed.
This commit is contained in:
parent
0b3863fb52
commit
eebc9ebd54
|
@ -52,11 +52,9 @@ SidedMessage = namedtuple("SidedMessage", ["side", "phase", "body",
|
||||||
"server_rx", "msg_id"])
|
"server_rx", "msg_id"])
|
||||||
|
|
||||||
class Mailbox:
|
class Mailbox:
|
||||||
def __init__(self, app, db, blur_usage, log_requests, app_id, mailbox_id):
|
def __init__(self, app, db, app_id, mailbox_id):
|
||||||
self._app = app
|
self._app = app
|
||||||
self._db = db
|
self._db = db
|
||||||
self._blur_usage = blur_usage
|
|
||||||
self._log_requests = log_requests
|
|
||||||
self._app_id = app_id
|
self._app_id = app_id
|
||||||
self._mailbox_id = mailbox_id
|
self._mailbox_id = mailbox_id
|
||||||
self._listeners = {} # handle -> (send_f, stop_f)
|
self._listeners = {} # handle -> (send_f, stop_f)
|
||||||
|
@ -99,12 +97,16 @@ class Mailbox:
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def add_listener(self, handle, send_f, stop_f):
|
def add_listener(self, handle, send_f, stop_f):
|
||||||
|
# TODO: update 'updated'
|
||||||
self._listeners[handle] = (send_f, stop_f)
|
self._listeners[handle] = (send_f, stop_f)
|
||||||
return self.get_messages()
|
return self.get_messages()
|
||||||
|
|
||||||
def remove_listener(self, handle):
|
def remove_listener(self, handle):
|
||||||
self._listeners.pop(handle)
|
self._listeners.pop(handle)
|
||||||
|
|
||||||
|
def has_listeners(self):
|
||||||
|
return bool(self._listeners)
|
||||||
|
|
||||||
def broadcast_message(self, sm):
|
def broadcast_message(self, sm):
|
||||||
for (send_f, stop_f) in self._listeners.values():
|
for (send_f, stop_f) in self._listeners.values():
|
||||||
send_f(sm)
|
send_f(sm)
|
||||||
|
@ -133,11 +135,8 @@ class Mailbox:
|
||||||
return
|
return
|
||||||
sr = remove_side(row, side)
|
sr = remove_side(row, side)
|
||||||
if sr.empty:
|
if sr.empty:
|
||||||
rows = db.execute("SELECT DISTINCT(`side`) FROM `messages`"
|
self._app._summarize_mailbox_and_store(self._mailbox_id, row,
|
||||||
" WHERE `app_id`=? AND `mailbox_id`=?",
|
mood, when, pruned=False)
|
||||||
(self._app_id, self._mailbox_id)).fetchall()
|
|
||||||
num_sides = len(rows)
|
|
||||||
self._summarize_and_store(row, num_sides, mood, when, pruned=False)
|
|
||||||
self._delete()
|
self._delete()
|
||||||
db.commit()
|
db.commit()
|
||||||
elif sr.changed:
|
elif sr.changed:
|
||||||
|
@ -164,61 +163,8 @@ class Mailbox:
|
||||||
|
|
||||||
self._app.free_mailbox(self._mailbox_id)
|
self._app.free_mailbox(self._mailbox_id)
|
||||||
|
|
||||||
def _summarize_and_store(self, row, num_sides, second_mood, delete_time,
|
def is_active(self):
|
||||||
pruned):
|
return bool(self._listeners)
|
||||||
u = self._summarize(row, num_sides, second_mood, delete_time, pruned)
|
|
||||||
self._db.execute("INSERT INTO `mailbox_usage`"
|
|
||||||
" (`app_id`, "
|
|
||||||
" `started`, `total_time`, `waiting_time`, `result`)"
|
|
||||||
" VALUES (?, ?,?,?,?)",
|
|
||||||
(self._app_id,
|
|
||||||
u.started, u.total_time, u.waiting_time, u.result))
|
|
||||||
|
|
||||||
def _summarize(self, row, num_sides, second_mood, delete_time, pruned):
|
|
||||||
started = row["started"]
|
|
||||||
if self._blur_usage:
|
|
||||||
started = self._blur_usage * (started // self._blur_usage)
|
|
||||||
waiting_time = None
|
|
||||||
if row["second"]:
|
|
||||||
waiting_time = row["second"] - row["started"]
|
|
||||||
total_time = delete_time - row["started"]
|
|
||||||
|
|
||||||
if num_sides == 0:
|
|
||||||
result = u"quiet"
|
|
||||||
elif num_sides == 1:
|
|
||||||
result = u"lonely"
|
|
||||||
else:
|
|
||||||
result = u"happy"
|
|
||||||
|
|
||||||
moods = set([row["first_mood"], second_mood])
|
|
||||||
if u"lonely" in moods:
|
|
||||||
result = u"lonely"
|
|
||||||
if u"errory" in moods:
|
|
||||||
result = u"errory"
|
|
||||||
if u"scary" in moods:
|
|
||||||
result = u"scary"
|
|
||||||
if pruned:
|
|
||||||
result = u"pruney"
|
|
||||||
if row["crowded"]:
|
|
||||||
result = u"crowded"
|
|
||||||
|
|
||||||
return Usage(started=started, waiting_time=waiting_time,
|
|
||||||
total_time=total_time, result=result)
|
|
||||||
|
|
||||||
def is_idle(self):
|
|
||||||
if self._listeners:
|
|
||||||
return False
|
|
||||||
c = self._db.execute("SELECT `server_rx` FROM `messages`"
|
|
||||||
" WHERE `app_id`=? AND `mailbox_id`=?"
|
|
||||||
" ORDER BY `server_rx` DESC LIMIT 1",
|
|
||||||
(self._app_id, self._mailbox_id))
|
|
||||||
rows = c.fetchall()
|
|
||||||
if not rows:
|
|
||||||
return True
|
|
||||||
old = time.time() - CHANNEL_EXPIRATION_TIME
|
|
||||||
if rows[0]["server_rx"] < old:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _shutdown(self):
|
def _shutdown(self):
|
||||||
# used at test shutdown to accelerate client disconnects
|
# used at test shutdown to accelerate client disconnects
|
||||||
|
@ -226,14 +172,22 @@ class Mailbox:
|
||||||
stop_f()
|
stop_f()
|
||||||
|
|
||||||
class AppNamespace:
|
class AppNamespace:
|
||||||
def __init__(self, db, welcome, blur_usage, log_requests, app_id):
|
def __init__(self, db, blur_usage, log_requests, app_id):
|
||||||
self._db = db
|
self._db = db
|
||||||
self._welcome = welcome
|
|
||||||
self._blur_usage = blur_usage
|
self._blur_usage = blur_usage
|
||||||
self._log_requests = log_requests
|
self._log_requests = log_requests
|
||||||
self._app_id = app_id
|
self._app_id = app_id
|
||||||
self._mailboxes = {}
|
self._mailboxes = {}
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
# An idle AppNamespace does not need to be kept in memory: it can be
|
||||||
|
# reconstructed from the DB if needed. And active one must be kept
|
||||||
|
# alive.
|
||||||
|
for mb in self._mailboxes.values():
|
||||||
|
if mb.is_active():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def get_nameplate_ids(self):
|
def get_nameplate_ids(self):
|
||||||
db = self._db
|
db = self._db
|
||||||
# TODO: filter this to numeric ids?
|
# TODO: filter this to numeric ids?
|
||||||
|
@ -265,7 +219,7 @@ class AppNamespace:
|
||||||
del mailbox_id # ignored, they'll learn it from claim()
|
del mailbox_id # ignored, they'll learn it from claim()
|
||||||
return nameplate_id
|
return nameplate_id
|
||||||
|
|
||||||
def claim_nameplate(self, nameplate_id, side, when):
|
def claim_nameplate(self, nameplate_id, side, when, _test_mailbox_id=None):
|
||||||
# when we're done:
|
# when we're done:
|
||||||
# * there will be one row for the nameplate
|
# * there will be one row for the nameplate
|
||||||
# * side1 or side2 will be populated
|
# * side1 or side2 will be populated
|
||||||
|
@ -298,7 +252,10 @@ class AppNamespace:
|
||||||
if self._log_requests:
|
if self._log_requests:
|
||||||
log.msg("creating nameplate#%s for app_id %s" %
|
log.msg("creating nameplate#%s for app_id %s" %
|
||||||
(nameplate_id, self._app_id))
|
(nameplate_id, self._app_id))
|
||||||
mailbox_id = generate_mailbox_id()
|
if _test_mailbox_id is not None: # for unit tests
|
||||||
|
mailbox_id = _test_mailbox_id
|
||||||
|
else:
|
||||||
|
mailbox_id = generate_mailbox_id()
|
||||||
db.execute("INSERT INTO `nameplates`"
|
db.execute("INSERT INTO `nameplates`"
|
||||||
" (`app_id`, `id`, `mailbox_id`, `side1`, `crowded`,"
|
" (`app_id`, `id`, `mailbox_id`, `side1`, `crowded`,"
|
||||||
" `updated`, `started`)"
|
" `updated`, `started`)"
|
||||||
|
@ -365,25 +322,6 @@ class AppNamespace:
|
||||||
return Usage(started=started, waiting_time=waiting_time,
|
return Usage(started=started, waiting_time=waiting_time,
|
||||||
total_time=total_time, result=result)
|
total_time=total_time, result=result)
|
||||||
|
|
||||||
def _prune_nameplate(self, row, delete_time):
|
|
||||||
# requires caller to db.commit()
|
|
||||||
db = self._db
|
|
||||||
db.execute("DELETE FROM `nameplates` WHERE `app_id`=? AND `id`=?",
|
|
||||||
(self._app_id, row["id"]))
|
|
||||||
self._summarize_nameplate_and_store(row, delete_time, pruned=True)
|
|
||||||
# TODO: make a Nameplate object, keep track of when there's a
|
|
||||||
# websocket that's watching it, don't prune a nameplate that someone
|
|
||||||
# is watching, even if they started watching a long time ago
|
|
||||||
|
|
||||||
def prune_nameplates(self, old):
|
|
||||||
db = self._db
|
|
||||||
for row in db.execute("SELECT * FROM `nameplates`"
|
|
||||||
" WHERE `updated` < ?",
|
|
||||||
(old,)).fetchall():
|
|
||||||
self._prune_nameplate(row)
|
|
||||||
count = db.execute("SELECT COUNT(*) FROM `nameplates`").fetchone()[0]
|
|
||||||
return count
|
|
||||||
|
|
||||||
def open_mailbox(self, mailbox_id, side, when):
|
def open_mailbox(self, mailbox_id, side, when):
|
||||||
assert isinstance(mailbox_id, type(u"")), type(mailbox_id)
|
assert isinstance(mailbox_id, type(u"")), type(mailbox_id)
|
||||||
db = self._db
|
db = self._db
|
||||||
|
@ -398,8 +336,6 @@ class AppNamespace:
|
||||||
db.commit() # XXX
|
db.commit() # XXX
|
||||||
# mailbox.open() does a SELECT to find the old sides
|
# mailbox.open() does a SELECT to find the old sides
|
||||||
self._mailboxes[mailbox_id] = Mailbox(self, self._db,
|
self._mailboxes[mailbox_id] = Mailbox(self, self._db,
|
||||||
self._blur_usage,
|
|
||||||
self._log_requests,
|
|
||||||
self._app_id, mailbox_id)
|
self._app_id, mailbox_id)
|
||||||
mailbox = self._mailboxes[mailbox_id]
|
mailbox = self._mailboxes[mailbox_id]
|
||||||
mailbox.open(side, when)
|
mailbox.open(side, when)
|
||||||
|
@ -416,25 +352,178 @@ class AppNamespace:
|
||||||
# log.msg("freed+killed #%s, now have %d DB mailboxes, %d live" %
|
# log.msg("freed+killed #%s, now have %d DB mailboxes, %d live" %
|
||||||
# (mailbox_id, len(self.get_claimed()), len(self._mailboxes)))
|
# (mailbox_id, len(self.get_claimed()), len(self._mailboxes)))
|
||||||
|
|
||||||
def prune_mailboxes(self, old):
|
def _summarize_mailbox_and_store(self, mailbox_id, row,
|
||||||
|
second_mood, delete_time, pruned):
|
||||||
|
db = self._db
|
||||||
|
rows = db.execute("SELECT DISTINCT(`side`) FROM `messages`"
|
||||||
|
" WHERE `app_id`=? AND `mailbox_id`=?",
|
||||||
|
(self._app_id, mailbox_id)).fetchall()
|
||||||
|
num_sides = len(rows)
|
||||||
|
u = self._summarize_mailbox(row, num_sides, second_mood, delete_time,
|
||||||
|
pruned)
|
||||||
|
db.execute("INSERT INTO `mailbox_usage`"
|
||||||
|
" (`app_id`,"
|
||||||
|
" `started`, `total_time`, `waiting_time`, `result`)"
|
||||||
|
" VALUES (?, ?,?,?,?)",
|
||||||
|
(self._app_id,
|
||||||
|
u.started, u.total_time, u.waiting_time, u.result))
|
||||||
|
|
||||||
|
def _summarize_mailbox(self, row, num_sides, second_mood, delete_time,
|
||||||
|
pruned):
|
||||||
|
started = row["started"]
|
||||||
|
if self._blur_usage:
|
||||||
|
started = self._blur_usage * (started // self._blur_usage)
|
||||||
|
waiting_time = None
|
||||||
|
if row["second"]:
|
||||||
|
waiting_time = row["second"] - row["started"]
|
||||||
|
total_time = delete_time - row["started"]
|
||||||
|
|
||||||
|
if num_sides == 0:
|
||||||
|
result = u"quiet"
|
||||||
|
elif num_sides == 1:
|
||||||
|
result = u"lonely"
|
||||||
|
else:
|
||||||
|
result = u"happy"
|
||||||
|
|
||||||
|
moods = set([row["first_mood"], second_mood])
|
||||||
|
if u"lonely" in moods:
|
||||||
|
result = u"lonely"
|
||||||
|
if u"errory" in moods:
|
||||||
|
result = u"errory"
|
||||||
|
if u"scary" in moods:
|
||||||
|
result = u"scary"
|
||||||
|
if pruned:
|
||||||
|
result = u"pruney"
|
||||||
|
if row["crowded"]:
|
||||||
|
result = u"crowded"
|
||||||
|
|
||||||
|
return Usage(started=started, waiting_time=waiting_time,
|
||||||
|
total_time=total_time, result=result)
|
||||||
|
|
||||||
|
def prune(self, now, old):
|
||||||
# For now, pruning is logged even if log_requests is False, to debug
|
# For now, pruning is logged even if log_requests is False, to debug
|
||||||
# the pruning process, and since pruning is triggered by a timer
|
# the pruning process, and since pruning is triggered by a timer
|
||||||
# instead of by user action. It does reveal which mailboxes were
|
# instead of by user action. It does reveal which mailboxes were
|
||||||
# present when the pruning process began, though, so in the log run
|
# present when the pruning process began, though, so in the log run
|
||||||
# it should do less logging.
|
# it should do less logging.
|
||||||
log.msg(" channel prune begins")
|
log.msg(" prune begins (%s)" % self._app_id)
|
||||||
# a channel is deleted when there are no listeners and there have
|
db = self._db
|
||||||
# been no messages added in CHANNEL_EXPIRATION_TIME seconds
|
modified = False
|
||||||
mailboxes = set(self.get_claimed()) # these have messages
|
# for all `mailboxes`: classify as new or old
|
||||||
mailboxes.update(self._mailboxes) # these might have listeners
|
OLD = 0; NEW = 1
|
||||||
for mailbox_id in mailboxes:
|
all_mailboxes = {}
|
||||||
log.msg(" channel prune checking %d" % mailbox_id)
|
all_mailbox_rows = {}
|
||||||
channel = self.get_channel(mailbox_id)
|
for row in db.execute("SELECT * FROM `mailboxes`"
|
||||||
if channel.is_idle():
|
" WHERE `app_id`=?",
|
||||||
log.msg(" channel prune expiring %d" % mailbox_id)
|
(self._app_id,)).fetchall():
|
||||||
channel.delete_and_summarize() # calls self.free_channel
|
mailbox_id = row["id"]
|
||||||
log.msg(" channel prune done, %r left" % (self._mailboxes.keys(),))
|
all_mailbox_rows[mailbox_id] = row
|
||||||
return bool(self._mailboxes)
|
if row["started"] > old:
|
||||||
|
which = NEW
|
||||||
|
elif row["second"] and row["second"] > old:
|
||||||
|
which = NEW
|
||||||
|
else:
|
||||||
|
which = OLD
|
||||||
|
all_mailboxes[mailbox_id] = which
|
||||||
|
#log.msg(" 2: all_mailboxes", all_mailboxes, all_mailbox_rows)
|
||||||
|
|
||||||
|
# for all mailbox ids used by `messages`:
|
||||||
|
# if there is no matching mailbox: delete the messages
|
||||||
|
# if there is at least one new message (select when>old limit 1):
|
||||||
|
# classify the mailbox as new
|
||||||
|
for row in db.execute("SELECT DISTINCT(`mailbox_id`)"
|
||||||
|
" FROM `messages`"
|
||||||
|
" WHERE `app_id`=?",
|
||||||
|
(self._app_id,)).fetchall():
|
||||||
|
mailbox_id = row["mailbox_id"]
|
||||||
|
if mailbox_id not in all_mailboxes:
|
||||||
|
log.msg(" deleting orphan messages", mailbox_id)
|
||||||
|
db.execute("DELETE FROM `messages`"
|
||||||
|
" WHERE `app_id`=? AND `mailbox_id`=?",
|
||||||
|
(self._app_id, mailbox_id))
|
||||||
|
modified = True
|
||||||
|
else:
|
||||||
|
new_msgs = db.execute("SELECT * FROM `messages`"
|
||||||
|
" WHERE `app_id`=? AND `mailbox_id`=?"
|
||||||
|
" AND `server_rx` > ?"
|
||||||
|
" LIMIT 1",
|
||||||
|
(self._app_id, mailbox_id, old)
|
||||||
|
).fetchall()
|
||||||
|
if new_msgs:
|
||||||
|
#log.msg(" 3-: saved by new messages", new_msgs)
|
||||||
|
all_mailboxes[mailbox_id] = NEW
|
||||||
|
#log.msg(" 4: all_mailboxes", all_mailboxes)
|
||||||
|
|
||||||
|
# for all mailbox objects with active listeners:
|
||||||
|
# classify the mailbox as new
|
||||||
|
for mailbox_id in self._mailboxes:
|
||||||
|
#log.msg(" -5: checking", mailbox_id, self._mailboxes[mailbox_id])
|
||||||
|
if self._mailboxes[mailbox_id].has_listeners():
|
||||||
|
all_mailboxes[mailbox_id] = NEW
|
||||||
|
#log.msg(" 5: all_mailboxes", all_mailboxes)
|
||||||
|
|
||||||
|
# for all `nameplates`:
|
||||||
|
# classify as new or old
|
||||||
|
# if the linked mailbox exists:
|
||||||
|
# if it is new:
|
||||||
|
# classify nameplate as new
|
||||||
|
# if it is old:
|
||||||
|
# if the nameplate is new:
|
||||||
|
# classify mailbox as new
|
||||||
|
all_nameplates = {}
|
||||||
|
all_nameplate_rows = {}
|
||||||
|
for row in db.execute("SELECT * FROM `nameplates`"
|
||||||
|
" WHERE `app_id`=?",
|
||||||
|
(self._app_id,)).fetchall():
|
||||||
|
nameplate_id = row["id"]
|
||||||
|
all_nameplate_rows[nameplate_id] = row
|
||||||
|
if row["updated"] > old:
|
||||||
|
which = NEW
|
||||||
|
else:
|
||||||
|
which = OLD
|
||||||
|
mailbox_id = row["mailbox_id"]
|
||||||
|
if mailbox_id in all_mailboxes:
|
||||||
|
if all_mailboxes[mailbox_id] == NEW:
|
||||||
|
which = NEW
|
||||||
|
else:
|
||||||
|
if which == NEW:
|
||||||
|
all_mailboxes[mailbox_id] = NEW
|
||||||
|
all_nameplates[nameplate_id] = which
|
||||||
|
#log.msg(" 6: all_nameplates", all_nameplates, all_nameplate_rows)
|
||||||
|
|
||||||
|
# delete all old nameplates
|
||||||
|
# invariant check: if there is a linked mailbox, it is old
|
||||||
|
|
||||||
|
for nameplate_id, which in all_nameplates.items():
|
||||||
|
if which == OLD:
|
||||||
|
log.msg(" deleting nameplate", nameplate_id)
|
||||||
|
row = all_nameplate_rows[nameplate_id]
|
||||||
|
self._summarize_nameplate_and_store(row, now, pruned=True)
|
||||||
|
db.execute("DELETE FROM `nameplates`"
|
||||||
|
" WHERE `app_id`=? AND `id`=?",
|
||||||
|
(self._app_id, nameplate_id))
|
||||||
|
modified = True
|
||||||
|
|
||||||
|
# delete all messages for old mailboxes
|
||||||
|
# delete all old mailboxes
|
||||||
|
|
||||||
|
for mailbox_id, which in all_mailboxes.items():
|
||||||
|
if which == OLD:
|
||||||
|
log.msg(" deleting mailbox", mailbox_id)
|
||||||
|
self._summarize_mailbox_and_store(mailbox_id,
|
||||||
|
all_mailbox_rows[mailbox_id],
|
||||||
|
u"pruney", now, pruned=True)
|
||||||
|
db.execute("DELETE FROM `messages`"
|
||||||
|
" WHERE `app_id`=? AND `mailbox_id`=?",
|
||||||
|
(self._app_id, mailbox_id))
|
||||||
|
db.execute("DELETE FROM `mailboxes`"
|
||||||
|
" WHERE `app_id`=? AND `id`=?",
|
||||||
|
(self._app_id, mailbox_id))
|
||||||
|
modified = True
|
||||||
|
|
||||||
|
if modified:
|
||||||
|
db.commit()
|
||||||
|
log.msg(" prune complete, modified:", modified)
|
||||||
|
|
||||||
def _shutdown(self):
|
def _shutdown(self):
|
||||||
for channel in self._mailboxes.values():
|
for channel in self._mailboxes.values():
|
||||||
|
@ -462,25 +551,35 @@ class Rendezvous(service.MultiService):
|
||||||
if not app_id in self._apps:
|
if not app_id in self._apps:
|
||||||
if self._log_requests:
|
if self._log_requests:
|
||||||
log.msg("spawning app_id %s" % (app_id,))
|
log.msg("spawning app_id %s" % (app_id,))
|
||||||
self._apps[app_id] = AppNamespace(self._db, self._welcome,
|
self._apps[app_id] = AppNamespace(self._db,
|
||||||
self._blur_usage,
|
self._blur_usage,
|
||||||
self._log_requests, app_id)
|
self._log_requests, app_id)
|
||||||
return self._apps[app_id]
|
return self._apps[app_id]
|
||||||
|
|
||||||
def prune(self, old=None):
|
def get_all_apps(self):
|
||||||
|
apps = set()
|
||||||
|
for row in self._db.execute("SELECT DISTINCT `app_id`"
|
||||||
|
" FROM `nameplates`").fetchall():
|
||||||
|
apps.add(row["app_id"])
|
||||||
|
for row in self._db.execute("SELECT DISTINCT `app_id`"
|
||||||
|
" FROM `mailboxes`").fetchall():
|
||||||
|
apps.add(row["app_id"])
|
||||||
|
for row in self._db.execute("SELECT DISTINCT `app_id`"
|
||||||
|
" FROM `messages`").fetchall():
|
||||||
|
apps.add(row["app_id"])
|
||||||
|
return apps
|
||||||
|
|
||||||
|
def prune(self, now=None, old=None):
|
||||||
# As with AppNamespace.prune_old_mailboxes, we log for now.
|
# As with AppNamespace.prune_old_mailboxes, we log for now.
|
||||||
log.msg("beginning app prune")
|
log.msg("beginning app prune")
|
||||||
if old is None:
|
now = now or time.time()
|
||||||
old = time.time() - CHANNEL_EXPIRATION_TIME
|
old = old or (now - CHANNEL_EXPIRATION_TIME)
|
||||||
c = self._db.execute("SELECT DISTINCT `app_id` FROM `messages`")
|
for app_id in sorted(self.get_all_apps()):
|
||||||
apps = set([row["app_id"] for row in c.fetchall()]) # these have messages
|
|
||||||
apps.update(self._apps) # these might have listeners
|
|
||||||
for app_id in apps:
|
|
||||||
log.msg(" app prune checking %r" % (app_id,))
|
log.msg(" app prune checking %r" % (app_id,))
|
||||||
app = self.get_app(app_id)
|
app = self.get_app(app_id)
|
||||||
still_active = app.prune_nameplates(old) + app.prune_mailboxes(old)
|
app.prune(now, old)
|
||||||
if not still_active:
|
if not app.is_active(): # meaning no websockets
|
||||||
log.msg("prune pops app %r" % (app_id,))
|
log.msg(" pruning idle app", app_id)
|
||||||
self._apps.pop(app_id)
|
self._apps.pop(app_id)
|
||||||
log.msg("app prune ends, %d remaining apps" % len(self._apps))
|
log.msg("app prune ends, %d remaining apps" % len(self._apps))
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import json, itertools
|
import json, itertools
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
import mock
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
from twisted.python import log
|
||||||
from twisted.internet import protocol, reactor, defer
|
from twisted.internet import protocol, reactor, defer
|
||||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||||
from twisted.internet.endpoints import clientFromString, connectProtocol
|
from twisted.internet.endpoints import clientFromString, connectProtocol
|
||||||
|
@ -9,7 +11,8 @@ from autobahn.twisted import websocket
|
||||||
from .. import __version__
|
from .. import __version__
|
||||||
from .common import ServerBase
|
from .common import ServerBase
|
||||||
from ..server import rendezvous, transit_server
|
from ..server import rendezvous, transit_server
|
||||||
from ..server.rendezvous import Usage, SidedMessage
|
from ..server.rendezvous import Usage, SidedMessage, Mailbox
|
||||||
|
from ..server.database import get_db
|
||||||
|
|
||||||
class Server(ServerBase, unittest.TestCase):
|
class Server(ServerBase, unittest.TestCase):
|
||||||
def test_apps(self):
|
def test_apps(self):
|
||||||
|
@ -281,6 +284,175 @@ class Server(ServerBase, unittest.TestCase):
|
||||||
self.assertEqual(len(msgs), 5)
|
self.assertEqual(len(msgs), 5)
|
||||||
self.assertEqual(msgs[-1]["body"], u"body")
|
self.assertEqual(msgs[-1]["body"], u"body")
|
||||||
|
|
||||||
|
class Prune(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_apps(self):
|
||||||
|
rv = rendezvous.Rendezvous(get_db(":memory:"), None, None)
|
||||||
|
app = rv.get_app(u"appid")
|
||||||
|
app.allocate_nameplate(u"side", 121)
|
||||||
|
app.prune = mock.Mock()
|
||||||
|
rv.prune(now=123, old=122)
|
||||||
|
self.assertEqual(app.prune.mock_calls, [mock.call(123, 122)])
|
||||||
|
|
||||||
|
def test_active(self):
|
||||||
|
rv = rendezvous.Rendezvous(get_db(":memory:"), None, None)
|
||||||
|
app = rv.get_app(u"appid1")
|
||||||
|
self.assertFalse(app.is_active())
|
||||||
|
|
||||||
|
mb = app.open_mailbox(u"mbid", u"side1", 0)
|
||||||
|
self.assertFalse(mb.is_active())
|
||||||
|
self.assertFalse(app.is_active())
|
||||||
|
|
||||||
|
mb.add_listener(u"handle", None, None)
|
||||||
|
self.assertTrue(mb.is_active())
|
||||||
|
self.assertTrue(app.is_active())
|
||||||
|
|
||||||
|
mb.remove_listener(u"handle")
|
||||||
|
self.assertFalse(mb.is_active())
|
||||||
|
self.assertFalse(app.is_active())
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
db = get_db(":memory:")
|
||||||
|
rv = rendezvous.Rendezvous(db, None, 3600)
|
||||||
|
|
||||||
|
# timestamps <=50 are "old", >=51 are "new"
|
||||||
|
#OLD = "old"; NEW = "new"
|
||||||
|
#when = {OLD: 1, NEW: 60}
|
||||||
|
new_nameplates = set()
|
||||||
|
new_mailboxes = set()
|
||||||
|
new_messages = set()
|
||||||
|
|
||||||
|
APPID = u"appid"
|
||||||
|
app = rv.get_app(APPID)
|
||||||
|
|
||||||
|
# Exercise the first-vs-second newness tests. These nameplates have
|
||||||
|
# no mailbox.
|
||||||
|
app.claim_nameplate(u"np-1", u"side1", 1)
|
||||||
|
app.claim_nameplate(u"np-2", u"side1", 1)
|
||||||
|
app.claim_nameplate(u"np-2", u"side2", 2)
|
||||||
|
app.claim_nameplate(u"np-3", u"side1", 60)
|
||||||
|
new_nameplates.add(u"np-3")
|
||||||
|
app.claim_nameplate(u"np-4", u"side1", 1)
|
||||||
|
app.claim_nameplate(u"np-4", u"side2", 60)
|
||||||
|
new_nameplates.add(u"np-4")
|
||||||
|
app.claim_nameplate(u"np-5", u"side1", 60)
|
||||||
|
app.claim_nameplate(u"np-5", u"side2", 61)
|
||||||
|
new_nameplates.add(u"np-5")
|
||||||
|
|
||||||
|
# same for mailboxes
|
||||||
|
app.open_mailbox(u"mb-11", u"side1", 1)
|
||||||
|
app.open_mailbox(u"mb-12", u"side1", 1)
|
||||||
|
app.open_mailbox(u"mb-12", u"side2", 2)
|
||||||
|
app.open_mailbox(u"mb-13", u"side1", 60)
|
||||||
|
new_mailboxes.add(u"mb-13")
|
||||||
|
app.open_mailbox(u"mb-14", u"side1", 1)
|
||||||
|
app.open_mailbox(u"mb-14", u"side2", 60)
|
||||||
|
new_mailboxes.add(u"mb-14")
|
||||||
|
app.open_mailbox(u"mb-15", u"side1", 60)
|
||||||
|
app.open_mailbox(u"mb-15", u"side2", 61)
|
||||||
|
new_mailboxes.add(u"mb-15")
|
||||||
|
|
||||||
|
rv.prune(now=123, old=50)
|
||||||
|
|
||||||
|
nameplates = set([row["id"] for row in
|
||||||
|
db.execute("SELECT * FROM `nameplates`").fetchall()])
|
||||||
|
self.assertEqual(new_nameplates, nameplates)
|
||||||
|
mailboxes = set([row["id"] for row in
|
||||||
|
db.execute("SELECT * FROM `mailboxes`").fetchall()])
|
||||||
|
self.assertEqual(new_mailboxes, mailboxes)
|
||||||
|
messages = set([row["msg_id"] for row in
|
||||||
|
db.execute("SELECT * FROM `messages`").fetchall()])
|
||||||
|
self.assertEqual(new_messages, messages)
|
||||||
|
|
||||||
|
def test_lots(self):
|
||||||
|
OLD = "old"; NEW = "new"
|
||||||
|
for nameplate in [None, OLD, NEW]:
|
||||||
|
for mailbox in [None, OLD, NEW]:
|
||||||
|
listeners = [False]
|
||||||
|
if mailbox is not None:
|
||||||
|
listeners = [False, True]
|
||||||
|
for has_listeners in listeners:
|
||||||
|
for messages in [None, OLD, NEW]:
|
||||||
|
self.one(nameplate, mailbox, has_listeners, messages)
|
||||||
|
|
||||||
|
#def test_one(self):
|
||||||
|
# # to debug specific problems found by test_lots
|
||||||
|
# self.one(None, "old", True, None)
|
||||||
|
|
||||||
|
def one(self, nameplate, mailbox, has_listeners, messages):
|
||||||
|
desc = ("nameplate=%s, mailbox=%s, has_listeners=%s,"
|
||||||
|
" messages=%s" %
|
||||||
|
(nameplate, mailbox, has_listeners, messages))
|
||||||
|
log.msg(desc)
|
||||||
|
|
||||||
|
db = get_db(":memory:")
|
||||||
|
rv = rendezvous.Rendezvous(db, None, 3600)
|
||||||
|
APPID = u"appid"
|
||||||
|
app = rv.get_app(APPID)
|
||||||
|
|
||||||
|
# timestamps <=50 are "old", >=51 are "new"
|
||||||
|
OLD = "old"; NEW = "new"
|
||||||
|
when = {OLD: 1, NEW: 60}
|
||||||
|
nameplate_survives = False
|
||||||
|
mailbox_survives = False
|
||||||
|
messages_survive = False
|
||||||
|
|
||||||
|
mbid = u"mbid"
|
||||||
|
if nameplate is not None:
|
||||||
|
app.claim_nameplate(u"npid", u"side1", when[nameplate],
|
||||||
|
_test_mailbox_id=mbid)
|
||||||
|
if mailbox is not None:
|
||||||
|
mb = app.open_mailbox(mbid, u"side1", when[mailbox])
|
||||||
|
else:
|
||||||
|
# We might want a Mailbox, because that's the easiest way to add
|
||||||
|
# a "messages" row, but we can't use app.open_mailbox() because
|
||||||
|
# that modifies both the "mailboxes" table and app._mailboxes,
|
||||||
|
# and sometimes we're testing what happens when there are
|
||||||
|
# messages but not a mailbox
|
||||||
|
mb = Mailbox(app, db, APPID, mbid)
|
||||||
|
# we need app._mailboxes to know about this, because that's
|
||||||
|
# where it looks to find listeners
|
||||||
|
app._mailboxes[mbid] = mb
|
||||||
|
|
||||||
|
if messages is not None:
|
||||||
|
sm = SidedMessage(u"side1", u"phase", u"body", when[messages],
|
||||||
|
u"msgid")
|
||||||
|
mb.add_message(sm)
|
||||||
|
|
||||||
|
if has_listeners:
|
||||||
|
mb.add_listener("handle", None, None)
|
||||||
|
|
||||||
|
if mailbox is None and messages is not None:
|
||||||
|
# orphaned messages, even new ones, can't keep a nameplate alive
|
||||||
|
messages = None
|
||||||
|
messages_survive = False
|
||||||
|
|
||||||
|
if (nameplate is NEW or mailbox is NEW
|
||||||
|
or has_listeners or messages is NEW):
|
||||||
|
if nameplate is not None:
|
||||||
|
nameplate_survives = True
|
||||||
|
if mailbox is not None:
|
||||||
|
mailbox_survives = True
|
||||||
|
if messages is not None:
|
||||||
|
messages_survive = True
|
||||||
|
|
||||||
|
rv.prune(now=123, old=50)
|
||||||
|
|
||||||
|
nameplates = set([row["id"] for row in
|
||||||
|
db.execute("SELECT * FROM `nameplates`").fetchall()])
|
||||||
|
self.assertEqual(nameplate_survives, bool(nameplates),
|
||||||
|
("nameplate", nameplate_survives, nameplates, desc))
|
||||||
|
|
||||||
|
mailboxes = set([row["id"] for row in
|
||||||
|
db.execute("SELECT * FROM `mailboxes`").fetchall()])
|
||||||
|
self.assertEqual(mailbox_survives, bool(mailboxes),
|
||||||
|
("mailbox", mailbox_survives, mailboxes, desc))
|
||||||
|
|
||||||
|
messages = set([row["msg_id"] for row in
|
||||||
|
db.execute("SELECT * FROM `messages`").fetchall()])
|
||||||
|
self.assertEqual(messages_survive, bool(messages),
|
||||||
|
("messages", messages_survive, messages, desc))
|
||||||
|
|
||||||
|
|
||||||
def strip_message(msg):
|
def strip_message(msg):
|
||||||
m2 = msg.copy()
|
m2 = msg.copy()
|
||||||
|
@ -726,14 +898,15 @@ class WebSocketAPI(ServerBase, unittest.TestCase):
|
||||||
|
|
||||||
class Summary(unittest.TestCase):
|
class Summary(unittest.TestCase):
|
||||||
def test_mailbox(self):
|
def test_mailbox(self):
|
||||||
c = rendezvous.Mailbox(None, None, None, False, None, None)
|
app = rendezvous.AppNamespace(None, None, False, None)
|
||||||
# starts at time 1, maybe gets second open at time 3, closes at 5
|
# starts at time 1, maybe gets second open at time 3, closes at 5
|
||||||
base_row = {u"started": 1, u"second": None,
|
base_row = {u"started": 1, u"second": None,
|
||||||
u"first_mood": None, u"crowded": False}
|
u"first_mood": None, u"crowded": False}
|
||||||
def summ(num_sides, second_mood=None, pruned=False, **kwargs):
|
def summ(num_sides, second_mood=None, pruned=False, **kwargs):
|
||||||
row = base_row.copy()
|
row = base_row.copy()
|
||||||
row.update(kwargs)
|
row.update(kwargs)
|
||||||
return c._summarize(row, num_sides, second_mood, 5, pruned)
|
return app._summarize_mailbox(row, num_sides, second_mood, 5,
|
||||||
|
pruned)
|
||||||
|
|
||||||
self.assertEqual(summ(1), Usage(1, None, 4, u"lonely"))
|
self.assertEqual(summ(1), Usage(1, None, 4, u"lonely"))
|
||||||
self.assertEqual(summ(1, u"lonely"), Usage(1, None, 4, u"lonely"))
|
self.assertEqual(summ(1, u"lonely"), Usage(1, None, 4, u"lonely"))
|
||||||
|
@ -764,7 +937,7 @@ class Summary(unittest.TestCase):
|
||||||
Usage(1, 2, 4, u"pruney"))
|
Usage(1, 2, 4, u"pruney"))
|
||||||
|
|
||||||
def test_nameplate(self):
|
def test_nameplate(self):
|
||||||
a = rendezvous.AppNamespace(None, None, None, False, None)
|
a = rendezvous.AppNamespace(None, None, False, None)
|
||||||
# starts at time 1, maybe gets second open at time 3, closes at 5
|
# starts at time 1, maybe gets second open at time 3, closes at 5
|
||||||
base_row = {u"started": 1, u"second": None, u"crowded": False}
|
base_row = {u"started": 1, u"second": None, u"crowded": False}
|
||||||
def summ(num_sides, pruned=False, **kwargs):
|
def summ(num_sides, pruned=False, **kwargs):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user