Skip to content

Commit 1a2c008

Browse files
authored
Merge pull request #32162 from nextcloud/enh/session-reopen
Avoid locking the php session
2 parents 232866d + 1b43fbe commit 1a2c008

7 files changed

Lines changed: 74 additions & 14 deletions

File tree

config/config.sample.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@
256256
*/
257257
'session_lifetime' => 60 * 60 * 24,
258258

259+
/**
260+
* `true` enabled a relaxed session timeout, where the session timeout would no longer be
261+
* handled by Nextcloud but by either the PHP garbage collection or the expiration of
262+
* potential other session backends like redis.
263+
*
264+
* This may lead to sessions being available for longer than what session_lifetime uses but
265+
* comes with performance benefits as sessions are no longer a locking operation for concurrent
266+
* requests.
267+
*/
268+
'session_relaxed_expiry' => false,
269+
259270
/**
260271
* Enable or disable session keep-alive when a user is logged in to the Web UI.
261272
* Enabling this sends a "heartbeat" to the server to keep it from timing out.

lib/base.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,9 @@ public static function initSession() {
445445
die();
446446
}
447447

448+
//try to set the session lifetime
448449
$sessionLifeTime = self::getSessionLifeTime();
450+
@ini_set('gc_maxlifetime', (string)$sessionLifeTime);
449451

450452
// session timeout
451453
if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
@@ -455,7 +457,10 @@ public static function initSession() {
455457
\OC::$server->getUserSession()->logout();
456458
}
457459

458-
$session->set('LAST_ACTIVITY', time());
460+
if (!self::hasSessionRelaxedExpiry()) {
461+
$session->set('LAST_ACTIVITY', time());
462+
}
463+
$session->close();
459464
}
460465

461466
/**
@@ -465,6 +470,13 @@ private static function getSessionLifeTime() {
465470
return \OC::$server->getConfig()->getSystemValue('session_lifetime', 60 * 60 * 24);
466471
}
467472

473+
/**
474+
* @return bool true if the session expiry should only be done by gc instead of an explicit timeout
475+
*/
476+
public static function hasSessionRelaxedExpiry(): bool {
477+
return \OC::$server->getConfig()->getSystemValue('session_relaxed_expiry', false);
478+
}
479+
468480
/**
469481
* Try to set some values to the required Nextcloud default
470482
*/
@@ -707,9 +719,6 @@ public static function init() {
707719
$config->deleteAppValue('core', 'cronErrors');
708720
}
709721
}
710-
//try to set the session lifetime
711-
$sessionLifeTime = self::getSessionLifeTime();
712-
@ini_set('gc_maxlifetime', (string)$sessionLifeTime);
713722

714723
// User and Groups
715724
if (!$systemConfig->getValue("installed", false)) {

lib/private/AppFramework/Middleware/SessionMiddleware.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ public function __construct(ControllerMethodReflector $reflector,
5151
*/
5252
public function beforeController($controller, $methodName) {
5353
$useSession = $this->reflector->hasAnnotation('UseSession');
54-
if (!$useSession) {
55-
$this->session->close();
54+
if ($useSession) {
55+
$this->session->reopen();
5656
}
5757
}
5858

lib/private/Session/CryptoSessionData.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,17 @@ protected function initializeSession() {
9797
* @param mixed $value
9898
*/
9999
public function set(string $key, $value) {
100+
if ($this->get($key) === $value) {
101+
// Do not write the session if the value hasn't changed to avoid reopening
102+
return;
103+
}
104+
105+
$reopened = $this->reopen();
100106
$this->sessionValues[$key] = $value;
101107
$this->isModified = true;
108+
if ($reopened) {
109+
$this->close();
110+
}
102111
}
103112

104113
/**
@@ -131,9 +140,13 @@ public function exists(string $key): bool {
131140
* @param string $key
132141
*/
133142
public function remove(string $key) {
143+
$reopened = $this->reopen();
134144
$this->isModified = true;
135145
unset($this->sessionValues[$key]);
136146
$this->session->remove(self::encryptedSessionName);
147+
if ($reopened) {
148+
$this->close();
149+
}
137150
}
138151

139152
/**
@@ -149,6 +162,10 @@ public function clear() {
149162
$this->session->clear();
150163
}
151164

165+
public function reopen(): bool {
166+
return $this->session->reopen();
167+
}
168+
152169
/**
153170
* Wrapper around session_regenerate_id
154171
*

lib/private/Session/Internal.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ public function __construct(string $name) {
6868
* @param integer $value
6969
*/
7070
public function set(string $key, $value) {
71-
$this->validateSession();
71+
$reopened = $this->reopen();
7272
$_SESSION[$key] = $value;
73+
if ($reopened) {
74+
$this->close();
75+
}
7376
}
7477

7578
/**
@@ -101,6 +104,7 @@ public function remove(string $key) {
101104
}
102105

103106
public function clear() {
107+
$this->reopen();
104108
$this->invoke('session_unset');
105109
$this->regenerateId();
106110
$this->startSession(true);
@@ -120,6 +124,7 @@ public function close() {
120124
* @return void
121125
*/
122126
public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) {
127+
$this->reopen();
123128
$oldId = null;
124129

125130
if ($updateToken) {
@@ -171,8 +176,14 @@ public function getId(): string {
171176
/**
172177
* @throws \Exception
173178
*/
174-
public function reopen() {
175-
throw new \Exception('The session cannot be reopened - reopen() is only to be used in unit testing.');
179+
public function reopen(): bool {
180+
if ($this->sessionClosed) {
181+
$this->startSession(false, false);
182+
$this->sessionClosed = false;
183+
return true;
184+
}
185+
186+
return false;
176187
}
177188

178189
/**
@@ -214,7 +225,11 @@ private function invoke(string $functionName, array $parameters = [], bool $sile
214225
}
215226
}
216227

217-
private function startSession(bool $silence = false) {
218-
$this->invoke('session_start', [['cookie_samesite' => 'Lax']], $silence);
228+
private function startSession(bool $silence = false, bool $readAndClose = true) {
229+
$sessionParams = ['cookie_samesite' => 'Lax'];
230+
if (\OC::hasSessionRelaxedExpiry()) {
231+
$sessionParams['read_and_close'] = $readAndClose;
232+
}
233+
$this->invoke('session_start', [$sessionParams], $silence);
219234
}
220235
}

lib/private/Session/Memory.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ public function __construct(string $name) {
5353
* @param integer $value
5454
*/
5555
public function set(string $key, $value) {
56-
$this->validateSession();
5756
$this->data[$key] = $value;
5857
}
5958

@@ -80,7 +79,6 @@ public function exists(string $key): bool {
8079
* @param string $key
8180
*/
8281
public function remove(string $key) {
83-
$this->validateSession();
8482
unset($this->data[$key]);
8583
}
8684

@@ -110,8 +108,10 @@ public function getId(): string {
110108
/**
111109
* Helper function for PHPUnit execution - don't use in non-test code
112110
*/
113-
public function reopen() {
111+
public function reopen(): bool {
112+
$reopened = $this->sessionClosed;
114113
$this->sessionClosed = false;
114+
return $reopened;
115115
}
116116

117117
/**

lib/public/ISession.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ public function remove(string $key);
8383
*/
8484
public function clear();
8585

86+
/**
87+
* Reopen a session for writing again
88+
*
89+
* @return bool true if the session was actually reopened, otherwise false
90+
* @since 25.0.0
91+
*/
92+
public function reopen(): bool;
93+
8694
/**
8795
* Close the session and release the lock
8896
* @since 7.0.0

0 commit comments

Comments
 (0)