@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Expire and garbage collect unused sessions

Summary:
Ref T3720. Ref T4310. Currently, we limit the maximum number of concurrent sessions of each type. This is primarily because sessions predate garbage collection and we had no way to prevent the session table from growing fairly quickly and without bound unless we did this.

Now that we have GC (and it's modular!) we can just expire unused sessions after a while and throw them away:

- Add a `sessionExpires` column to the table, with a key.
- Add a GC for old sessions.
- When we establish a session, set `sessionExpires` to the current time plus the session TTL.
- When a user uses a session and has used up more than 20% of the time on it, extend the session.

In addition to this, we could also rotate sessions, but I think that provides very little value. If we do want to implement it, we should hold it until after T3720 / T4310.

Test Plan:
- Ran schema changes.
- Looked at database.
- Tested GC:
- Started GC.
- Set expires on one row to the past.
- Restarted GC.
- Verified GC nuked the session.
- Logged in.
- Logged out.
- Ran Conduit method.
- Tested refresh:
- Set threshold to 0.0001% instead of 20%.
- Loaded page.
- Saw a session extension ever few page loads.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4310, T3720

Differential Revision: https://secure.phabricator.com/D7976

+82 -6
+8
resources/sql/autopatches/20140115.auth.2.expires.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.phabricator_session 2 + ADD sessionExpires INT UNSIGNED NOT NULL; 3 + 4 + UPDATE {$NAMESPACE}_user.phabricator_session 5 + SET sessionExpires = UNIX_TIMESTAMP() + (60 * 60 * 24 * 30); 6 + 7 + ALTER TABLE {$NAMESPACE}_user.phabricator_session 8 + ADD KEY `key_expires` (sessionExpires);
+2
src/__phutil_library_map__.php
··· 1211 1211 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 1212 1212 'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php', 1213 1213 'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php', 1214 + 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', 1214 1215 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 1215 1216 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 1216 1217 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', ··· 3791 3792 1 => 'PhabricatorPolicyInterface', 3792 3793 ), 3793 3794 'PhabricatorAuthSessionEngine' => 'Phobject', 3795 + 'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector', 3794 3796 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3795 3797 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 3796 3798 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController',
+38 -6
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 5 5 public function loadUserForSession($session_type, $session_key) { 6 6 $session_table = new PhabricatorAuthSession(); 7 7 $user_table = new PhabricatorUser(); 8 - $conn_r = $session_table->establishConnection('w'); 8 + $conn_r = $session_table->establishConnection('r'); 9 + 10 + // NOTE: We're being clever here because this happens on every page load, 11 + // and by joining we can save a query. 9 12 10 13 $info = queryfx_one( 11 14 $conn_r, 12 - 'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID 15 + 'SELECT s.sessionExpires AS _sessionExpires, s.id AS _sessionID, u.* 16 + FROM %T u JOIN %T s ON u.phid = s.userPHID 13 17 AND s.type LIKE %> AND s.sessionKey = %s', 14 18 $user_table->getTableName(), 15 19 $session_table->getTableName(), ··· 20 24 return null; 21 25 } 22 26 27 + $expires = $info['_sessionExpires']; 28 + $id = $info['_sessionID']; 29 + unset($info['_sessionExpires']); 30 + unset($info['_sessionID']); 31 + 32 + $ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); 33 + 34 + // If more than 20% of the time on this session has been used, refresh the 35 + // TTL back up to the full duration. The idea here is that sessions are 36 + // good forever if used regularly, but get GC'd when they fall out of use. 37 + 38 + if (time() + (0.80 * $ttl) > $expires) { 39 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 40 + $conn_w = $session_table->establishConnection('w'); 41 + queryfx( 42 + $conn_w, 43 + 'UPDATE %T SET sessionExpires = UNIX_TIMESTAMP() + %d WHERE id = %d', 44 + $session_table->getTableName(), 45 + $ttl, 46 + $id); 47 + unset($unguarded); 48 + } 49 + 23 50 return $user_table->loadFromArray($info); 24 51 } 25 52 ··· 84 111 // Consume entropy to generate a new session key, forestalling the eventual 85 112 // heat death of the universe. 86 113 $session_key = Filesystem::readRandomCharacters(40); 114 + $session_ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); 87 115 88 116 // Load all the currently active sessions. 89 117 $sessions = queryfx_all( ··· 119 147 // care if we race here or not. 120 148 queryfx( 121 149 $conn_w, 122 - 'INSERT IGNORE INTO %T (userPHID, type, sessionKey, sessionStart) 123 - VALUES (%s, %s, %s, 0)', 150 + 'INSERT IGNORE INTO %T 151 + (userPHID, type, sessionKey, sessionStart, sessionExpires) 152 + VALUES (%s, %s, %s, 0, UNIX_TIMESTAMP() + %d)', 124 153 $session_table->getTableName(), 125 154 $identity_phid, 126 155 $establish_type, 127 - PhabricatorHash::digest($session_key)); 156 + PhabricatorHash::digest($session_key), 157 + $session_ttl); 128 158 break; 129 159 } 130 160 } ··· 144 174 145 175 queryfx( 146 176 $conn_w, 147 - 'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP() 177 + 'UPDATE %T SET sessionKey = %s, sessionStart = UNIX_TIMESTAMP(), 178 + sessionExpires = UNIX_TIMESTAMP() + %d 148 179 WHERE userPHID = %s AND type = %s AND sessionKey = %s', 149 180 $session_table->getTableName(), 150 181 PhabricatorHash::digest($session_key), 182 + $session_ttl, 151 183 $identity_phid, 152 184 $establish_type, 153 185 $expect_key);
+18
src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthSessionGarbageCollector 4 + extends PhabricatorGarbageCollector { 5 + 6 + public function collectGarbage() { 7 + $session_table = new PhabricatorAuthSession(); 8 + $conn_w = $session_table->establishConnection('w'); 9 + 10 + queryfx( 11 + $conn_w, 12 + 'DELETE FROM %T WHERE sessionExpires <= UNIX_TIMESTAMP() LIMIT 100', 13 + $session_table->getTableName()); 14 + 15 + return ($conn_w->getAffectedRows() == 100); 16 + } 17 + 18 + }
+13
src/applications/auth/storage/PhabricatorAuthSession.php
··· 10 10 protected $type; 11 11 protected $sessionKey; 12 12 protected $sessionStart; 13 + protected $sessionExpires; 13 14 14 15 private $identityObject = self::ATTACHABLE; 15 16 ··· 37 38 public function getIdentityObject() { 38 39 return $this->assertAttached($this->identityObject); 39 40 } 41 + 42 + public static function getSessionTypeTTL($session_type) { 43 + switch ($session_type) { 44 + case self::TYPE_WEB: 45 + return (60 * 60 * 24 * 30); // 30 days 46 + case self::TYPE_CONDUIT: 47 + return (60 * 60 * 24); // 24 hours 48 + default: 49 + throw new Exception(pht('Unknown session type "%s".', $session_type)); 50 + } 51 + } 52 + 40 53 41 54 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 42 55
+3
src/applications/settings/panel/PhabricatorSettingsPanelSessions.php
··· 60 60 substr($session->getSessionKey(), 0, 12), 61 61 $session->getType(), 62 62 phabricator_datetime($session->getSessionStart(), $viewer), 63 + phabricator_datetime($session->getSessionExpires(), $viewer), 63 64 ); 64 65 } 65 66 ··· 72 73 pht('Session'), 73 74 pht('Type'), 74 75 pht('Created'), 76 + pht('Expires'), 75 77 )); 76 78 $table->setColumnClasses( 77 79 array( 78 80 'wide', 79 81 'n', 80 82 '', 83 + 'right', 81 84 'right', 82 85 )); 83 86