-
Notifications
You must be signed in to change notification settings - Fork 54
Description
Pennant Version
1.16.0
Laravel Version
11.44.2
PHP Version
8.3.17
Database Driver & Version
MySQL 8.0.41-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))
Description
There appears to be a rare race condition when parallel requests attempt to resolve a feature's value from storage (using the database driver), within a database transaction. This only happens when resolving for the first time (i.e. when the feature + value have yet to be stored). Note that this is also within an unauthenticated context.
I have not been able to reproduce this without wrapping it in a DB transaction.
(Illuminate\\Database\\UniqueConstraintViolationException(code: 23000): SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'test-feature-__laravel_null' for key 'features.features_name_scope_unique' (Connection: mysql, SQL: insert into `features` (`created_at`, `name`, `scope`, `updated_at`, `value`) values (2025-03-26 20:13:02, test-feature, __laravel_null, 2025-03-26 20:13:02, false)) at /home/myproject/code/vendor/laravel/framework/src/Illuminate/Database/Connection.php:820)
Steps To Reproduce
- Configure your Pennant store to use the
database
driver. - Define a test feature in
AppServiceProvider.php
:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Feature::define('test-feature', Lottery::odds(1 / 2));
}
- Set up a test route like so:
Route::get('/test', function () {
DB::transaction(function (): void {
Feature::when(
feature: 'test-feature',
whenActive: fn () => Log::info('call external API 1'),
whenInactive: fn () => Log::info('call external API 2')
);
});
return response();
});
- Purge features from storage and send parallel requests:
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
use Laravel\Pennant\Feature;
// Truncate features table.
Feature::purge();
// Send requests in parallel.
$responses = Http::pool(
fn (Pool $pool) => [
$pool->get("http://myapp.test/test"),
$pool->get("http://myapp.test/test")
]
);