Repository: evennia/evennia

Issue:
Phantom sessions in sessionhandler
#### Brief summary of issue / Description of requested feature:
So today a number of players seemed to have an issue where they had bogus sessions connected to their player object, which prevented them from going `@ic`. It may have happened from a lengthy reload. Since logging out only killed their current session, they couldn't fix it by just logging.


#### Steps to reproduce the issue / Reasons for adding feature:

1. Not sure how to reproduce since it's only happened this one time. It did affect a dozen or so players, though.
2. At a guess, the phantom sessions may have been generated by them trying to go `@ic` when getting the recursion error.

#### Error output / Expected result of feature
Thousands of lines of 
```
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::] Traceback (most recent call last):
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/evennia/evennia/objects/objects.py", line 704, in move_to
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     self.announce_move_to(source_location)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/arx/typeclasses/characters.py", line 759, in announce_move_to
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     " from %s" % source_location.get_display_name(obj) if source_location else ""))
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/arx/typeclasses/mixins.py", line 560, in msg
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     super(MsgMixins, self).msg(text, from_obj, session, options, **kwargs)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/evennia/evennia/objects/objects.py", line 526, in msg
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     sessions = make_iter(session) if session else self.sessions.all()
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/evennia/evennia/objects/objects.py", line 101, in all
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     return self.get()
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/evennia/evennia/objects/objects.py", line 90, in get
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     return self.get(sessid=sessid)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/evennia/evennia/objects/objects.py", line 89, in get
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     self._recache()
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/evennia/evennia/objects/objects.py", line 63, in _recache
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     self.obj.save(update_fields=["db_sessid"])
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/evennia/evennia/utils/idmapper/models.py", line 382, in save
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     super(SharedMemoryModel, self).save(*args, **kwargs)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/base.py", line 708, in save
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     force_update=force_update, update_fields=update_fields)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/base.py", line 736, in save_base
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/base.py", line 801, in _save_table
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     forced_update)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/base.py", line 831, in _do_update
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     filtered = base_qs.filter(pk=pk_val)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/query.py", line 790, in filter
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     return self._filter_or_exclude(False, *args, **kwargs)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/query.py", line 808, in _filter_or_exclude
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     clone.query.add_q(Q(*args, **kwargs))
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1243, in add_q
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     clause, _ = self._add_q(q_object, self.used_aliases)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1269, in _add_q
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     allow_joins=allow_joins, split_subq=split_subq,
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1203, in build_filter
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     condition = self.build_lookup(lookups, col, value)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1099, in build_lookup
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     return final_lookup(lhs, rhs)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/lookups.py", line 19, in __init__
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     self.rhs = self.get_prep_lookup()
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/lookups.py", line 57, in get_prep_lookup
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     return self.lhs.output_field.get_prep_lookup(self.lookup_name, self.rhs)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 744, in get_prep_lookup
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     return self.get_prep_value(value)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 973, in get_prep_value
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     value = super(AutoField, self).get_prep_value(value)
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]   File "/home/tehom/arx/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 708, in get_prep_value
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::]     if isinstance(value, Promise):
2017-02-17T09:25:41-0500 [AMPProtocol,0,127.0.0.1] [::] RuntimeError: maximum recursion depth exceeded while calling a Python object
```
for a recursion error throughout my logs.


#### Extra information, such as Evennia revision/repo/branch, operating system and ideas for how to solve / implement:
Not sure how it can detect sessions that aren't being used. I should have saw if a regular `@reload` fixed it with the sync checks before I did a shutdown. My bad for not trying that.



Discussion:

Resolving patch:
diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py
--- a/evennia/objects/objects.py
+++ b/evennia/objects/objects.py
@@ -59,7 +59,7 @@ def _recache(self):
         self._sessid_cache = list(set(int(val) for val in (self.obj.db_sessid or "").split(",") if val))
         if any(sessid for sessid in self._sessid_cache if sessid not in _SESSIONS):
             # cache is out of sync with sessionhandler! Only retain the ones in the handler.
-            self.sessid_cache = [sessid for sessid in self._sessid_cache if sessid in _SESSIONS]
+            self._sessid_cache = [sessid for sessid in self._sessid_cache if sessid in _SESSIONS]
             self.obj.db_sessid = ",".join(str(val) for val in self._sessid_cache)
             self.obj.save(update_fields=["db_sessid"])
 


Test patch: