@@ -49,6 +49,8 @@ class PermissionRegistrar
4949
5050 private array $ wildcardPermissionsIndex = [];
5151
52+ private bool $ isLoadingPermissions = false ;
53+
5254 /**
5355 * PermissionRegistrar constructor.
5456 */
@@ -172,6 +174,7 @@ public function clearPermissionsCollection(): void
172174 {
173175 $ this ->permissions = null ;
174176 $ this ->wildcardPermissionsIndex = [];
177+ $ this ->isLoadingPermissions = false ;
175178 }
176179
177180 /**
@@ -187,24 +190,49 @@ public function clearClassPermissions()
187190 /**
188191 * Load permissions from cache
189192 * And turns permissions array into a \Illuminate\Database\Eloquent\Collection
193+ *
194+ * Thread-safe implementation to prevent race conditions in concurrent environments
195+ * (e.g., Laravel Octane, Swoole, parallel requests)
190196 */
191- private function loadPermissions (): void
197+ private function loadPermissions (int $ retries = 0 ): void
192198 {
199+ // First check (without lock) - fast path for already loaded permissions
193200 if ($ this ->permissions ) {
194201 return ;
195202 }
196203
197- $ this ->permissions = $ this ->cache ->remember (
198- $ this ->cacheKey , $ this ->cacheExpirationTime , fn () => $ this ->getSerializedPermissionsForCache ()
199- );
204+ // Prevent concurrent loading using a flag-based lock
205+ // This protects against cache stampede and duplicate database queries
206+ if ($ this ->isLoadingPermissions && $ retries < 10 ) {
207+ // Another thread is loading, wait and retry
208+ usleep (10000 ); // Wait 10ms
209+ $ retries ++;
210+
211+ // After wait, recursively check again if permissions were loaded
212+ $ this ->loadPermissions ($ retries );
200213
201- $ this ->alias = $ this ->permissions ['alias ' ];
214+ return ;
215+ }
202216
203- $ this ->hydrateRolesCache ();
217+ // Set loading flag to prevent concurrent loads
218+ $ this ->isLoadingPermissions = true ;
204219
205- $ this ->permissions = $ this ->getHydratedPermissionCollection ();
220+ try {
221+ $ this ->permissions = $ this ->cache ->remember (
222+ $ this ->cacheKey , $ this ->cacheExpirationTime , fn () => $ this ->getSerializedPermissionsForCache ()
223+ );
206224
207- $ this ->cachedRoles = $ this ->alias = $ this ->except = [];
225+ $ this ->alias = $ this ->permissions ['alias ' ];
226+
227+ $ this ->hydrateRolesCache ();
228+
229+ $ this ->permissions = $ this ->getHydratedPermissionCollection ();
230+
231+ $ this ->cachedRoles = $ this ->alias = $ this ->except = [];
232+ } finally {
233+ // Always release the loading flag, even if an exception occurs
234+ $ this ->isLoadingPermissions = false ;
235+ }
208236 }
209237
210238 /**
0 commit comments