Skip to content

Commit

Permalink
6.3.1: catch Error when Redis is crashing and not die in 500... (#3006)
Browse files Browse the repository at this point in the history
  • Loading branch information
ildyria authored Feb 15, 2025
1 parent b015ddb commit aeefbf9
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 8 deletions.
2 changes: 2 additions & 0 deletions app/Listeners/CacheListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ public function handle(CacheHit|CacheMissed|KeyForgotten|KeyWritten $event): voi
CacheHit::class => Log::debug('CacheListener: Hit for ' . $event->key),
KeyForgotten::class => Log::info('CacheListener: Forgetting key ' . $event->key),
KeyWritten::class => $this->keyWritten($event),
// @codeCoverageIgnoreStart
default => '',
// @codeCoverageIgnoreEnd
};
}

Expand Down
22 changes: 15 additions & 7 deletions app/Metadata/Cache/RouteCacher.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Exceptions\Internal\LycheeLogicException;
use Closure;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

/**
* RouteCacher also associate the route data with the cache key.
Expand Down Expand Up @@ -51,13 +52,20 @@ public function remember(
}

$value = $callback();
Cache::put($key, $value, $ttl);

// Update the list of keys for the given route.
$this->rememberRoute($route, $key);

// Update the tags for the given key.
$this->rememberTags($tags, $key);
try {
Cache::put($key, $value, $ttl);

// Update the list of keys for the given route.
$this->rememberRoute($route, $key);

// Update the tags for the given key.
$this->rememberTags($tags, $key);
// @codeCoverageIgnoreStart
} catch (\Exception $e) {
// If we can't cache the value, we will just return the value.
Log::error(__METHOD__ . ':' . __LINE__ . ' Could not cache the value.', ['exception' => $e]);
}
// @codeCoverageIgnoreEnd

return $value;
}
Expand Down
34 changes: 34 additions & 0 deletions database/migrations/2025_02_15_090409_bump_version060301.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2025 LycheeOrg.
*/

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

return new class() extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
DB::table('configs')->where('key', 'cache_ttl')->update(['value' => '86400']);
DB::table('configs')->where('key', 'version')->update(['value' => '060301']);
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
DB::table('configs')->where('key', 'cache_ttl')->update(['value' => '300']);
DB::table('configs')->where('key', 'version')->update(['value' => '060300']);
}
};
111 changes: 111 additions & 0 deletions tests/Unit/Caching/CacheListenerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2025 LycheeOrg.
*/

/**
* We don't care for unhandled exceptions in tests.
* It is the nature of a test to throw an exception.
* Without this suppression we had 100+ Linter warning in this file which
* don't help anything.
*
* @noinspection PhpDocMissingThrowsInspection
* @noinspection PhpUnhandledExceptionInspection
*/

namespace Tests\Unit\Caching;

use App\Listeners\CacheListener;
use App\Models\Configs;
use Illuminate\Cache\Events\CacheHit;
use Illuminate\Cache\Events\CacheMissed;
use Illuminate\Cache\Events\KeyForgotten;
use Illuminate\Cache\Events\KeyWritten;
use Illuminate\Support\Facades\Log;
use Tests\AbstractTestCase;

class CacheListenerTest extends AbstractTestCase
{
public function tearDown(): void
{
Configs::where('key', 'cache_event_logging')->update(['value' => '0']);
Configs::invalidateCache();
parent::tearDown();
}

public function testCacheListenerNever(): void
{
Log::shouldReceive('debug')->never();
Log::shouldReceive('info')->never();

Configs::where('key', 'cache_event_logging')->update(['value' => '0']);
Configs::invalidateCache();

$listener = new CacheListener();
$listener->handle(new CacheMissed('store', 'key'));
$listener->handle(new CacheMissed('store', 'lv:dev-lycheeOrg'));
}

public function testCacheListenerMissed(): void
{
Log::shouldReceive('debug')->once()->with('CacheListener: Miss for key');
Log::shouldReceive('info')->never();

Configs::where('key', 'cache_event_logging')->update(['value' => '1']);
Configs::invalidateCache();

$listener = new CacheListener();
$listener->handle(new CacheMissed('store', 'key'));
}

public function testCacheListenerHit(): void
{
Log::shouldReceive('debug')->once()->with('CacheListener: Hit for key');
Log::shouldReceive('info')->never();

Configs::where('key', 'cache_event_logging')->update(['value' => '1']);
Configs::invalidateCache();

$listener = new CacheListener();
$listener->handle(new CacheHit('store', 'key', 'value'));
}

public function testCacheListenerKeyForgotten(): void
{
Log::shouldReceive('debug')->never();
Log::shouldReceive('info')->once()->with('CacheListener: Forgetting key key');

Configs::where('key', 'cache_event_logging')->update(['value' => '1']);
Configs::invalidateCache();

$listener = new CacheListener();
$listener->handle(new KeyForgotten('store', 'key'));
}

public function testCacheListenerKeyWritten(): void
{
Log::shouldReceive('debug')->never();
Log::shouldReceive('info')->once()->with('CacheListener: Writing key key');

Configs::where('key', 'cache_event_logging')->update(['value' => '1']);
Configs::invalidateCache();

$listener = new CacheListener();
$listener->handle(new KeyWritten('store', 'key', 'value'));
}

public function testCacheListenerKeyWrittenApi(): void
{
Log::shouldReceive('info')->never();
Log::shouldReceive('debug')->once()->with('CacheListener: Writing key api/key with value: \'value\'');

Configs::where('key', 'cache_event_logging')->update(['value' => '1']);
Configs::invalidateCache();

$listener = new CacheListener();
$listener->handle(new KeyWritten('store', 'api/key', 'value'));
}
}
128 changes: 128 additions & 0 deletions tests/Unit/Caching/RouteCacherTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2025 LycheeOrg.
*/

/**
* We don't care for unhandled exceptions in tests.
* It is the nature of a test to throw an exception.
* Without this suppression we had 100+ Linter warning in this file which
* don't help anything.
*
* @noinspection PhpDocMissingThrowsInspection
* @noinspection PhpUnhandledExceptionInspection
*/

namespace Tests\Unit\Caching;

use App\Exceptions\Internal\LycheeLogicException;
use App\Metadata\Cache\RouteCacher;
use App\Models\Configs;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Tests\AbstractTestCase;

class RouteCacherTest extends AbstractTestCase
{
public function setUp(): void
{
parent::setUp();

// We log to make sure to catch the specific events.
Configs::where('key', 'cache_event_logging')->update(['value' => '1']);
Configs::invalidateCache();
}

public function tearDown(): void
{
Configs::where('key', 'cache_event_logging')->update(['value' => '0']);
Configs::invalidateCache();
parent::tearDown();
}

public function testRouteCacherHit(): void
{
$routeCacher = new RouteCacher();
Log::shouldReceive('info')->once();
Cache::put('key', 60);

Log::shouldReceive('debug')->once()->with('CacheListener: Hit for key');
Log::shouldReceive('info')->never();

$routeCacher->remember('key', 'route', 60, function () {
return 60;
}, ['tags']);
}

public function testRouteCacherMiss(): void
{
$routeCacher = new RouteCacher();
Log::shouldReceive('debug')->once()->with('CacheListener: Miss for key');
Log::shouldReceive('debug')->once()->with('CacheListener: Miss for route');
Log::shouldReceive('debug')->once()->with('CacheListener: Miss for T:tags');
Log::shouldReceive('info')->once()->with('CacheListener: Writing key key');
Log::shouldReceive('info')->once()->with('CacheListener: Writing key route');
Log::shouldReceive('info')->once()->with('CacheListener: Writing key T:tags');

$routeCacher->remember('key', 'route', 60, function () {
return 60;
}, ['tags']);
}

public function testRouteCacherForgetRouteException(): void
{
$routeCacher = new RouteCacher();
Log::shouldReceive('info')->once();
Cache::put('route', [60]);

Log::shouldReceive('debug')->once()->with('CacheListener: Hit for route');

$this->expectException(LycheeLogicException::class);
$routeCacher->forgetRoute('route');
Cache::forget('route');
}

public function testRouteCacherForgetRoute(): void
{
$routeCacher = new RouteCacher();
Log::shouldReceive('info')->twice();
Cache::put('route', ['forgetMe' => 'value']);
Cache::put('forgetMe', 'value');

Log::shouldReceive('debug')->once()->with('CacheListener: Hit for route');
Log::shouldReceive('info')->once()->with('CacheListener: Forgetting key forgetMe');
Log::shouldReceive('info')->once()->with('CacheListener: Forgetting key route');

$routeCacher->forgetRoute('route');
}

public function testRouteCacherForgetTagException(): void
{
$routeCacher = new RouteCacher();
Log::shouldReceive('info')->once();
Cache::put('T:tag', [60]);

Log::shouldReceive('debug')->once()->with('CacheListener: Hit for T:tag');

$this->expectException(LycheeLogicException::class);
$routeCacher->forgetTag('tag');
Cache::forget('T:tag');
}

public function testRouteCacherForgetTag(): void
{
$routeCacher = new RouteCacher();
Log::shouldReceive('info')->twice();
Cache::put('T:tag', ['forgetMe' => 'value']);
Cache::put('forgetMe', 'value');

Log::shouldReceive('debug')->once()->with('CacheListener: Hit for T:tag');
Log::shouldReceive('info')->once()->with('CacheListener: Forgetting key forgetMe');
Log::shouldReceive('info')->once()->with('CacheListener: Forgetting key T:tag');

$routeCacher->forgetTag('tag');
}
}
2 changes: 1 addition & 1 deletion version.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.3.0
6.3.1

0 comments on commit aeefbf9

Please sign in to comment.