Merge pull request #7514 from pixelfed/fix/nodeinfo-delivery-cooldown-columns

Give Nodeinfo crawler its own cooldown columns, separate from delivery
pull/7522/head
Shlee 1 month ago committed by GitHub
commit 2a5c8f45ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -54,8 +54,9 @@ class FetchNodeinfoPipeline implements ShouldBeUniqueUntilProcessing, ShouldQueu
if ($instance->nodeinfo_last_fetched &&
$instance->nodeinfo_last_fetched->gt(now()->subHours(12)) ||
$instance->delivery_timeout &&
$instance->delivery_next_after->gt(now())
$instance->nodeinfo_timeout &&
$instance->nodeinfo_next_after &&
$instance->nodeinfo_next_after->gt(now())
) {
return;
}
@ -69,14 +70,14 @@ class FetchNodeinfoPipeline implements ShouldBeUniqueUntilProcessing, ShouldQueu
$instance->user_count = Profile::whereDomain($instance->domain)->count();
$instance->nodeinfo_last_fetched = now();
$instance->last_crawled_at = now();
$instance->delivery_timeout = 0;
$instance->nodeinfo_timeout = 0;
$instance->save();
}
} else {
$instance->user_count = Profile::whereDomain($instance->domain)->count();
$instance->delivery_timeout = 1;
$instance->nodeinfo_timeout = 1;
$instance->last_crawled_at = now();
$instance->delivery_next_after = now()->addHours(14);
$instance->nodeinfo_next_after = now()->addHours(14);
$instance->save();
}
}

@ -97,6 +97,7 @@ class Instance extends Model
'notes' => 'array',
'nodeinfo_last_fetched' => 'datetime',
'delivery_next_after' => 'datetime',
'nodeinfo_next_after' => 'datetime',
];
}

@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Nodeinfo crawling reused the ActivityPub delivery-backoff columns
// (delivery_timeout / delivery_next_after), so a failed Nodeinfo fetch
// could mark a sub-threshold host unavailable for delivery. Give the
// Nodeinfo crawler its own cooldown columns.
Schema::table('instances', function (Blueprint $table) {
if (! Schema::hasColumn('instances', 'nodeinfo_timeout')) {
$table->boolean('nodeinfo_timeout')->default(false);
}
if (! Schema::hasColumn('instances', 'nodeinfo_next_after')) {
$table->timestamp('nodeinfo_next_after')->nullable();
}
});
// Backfill: any host below the delivery failure threshold should not be
// carrying a delivery backoff. Nodeinfo failures previously wrote these
// columns without touching delivery_failures, so clear that stale state
// (applyFailure() re-arms it on the next threshold-crossing failure).
$threshold = max(1, (int) config('federation.activitypub.delivery.failure_threshold', 5));
DB::table('instances')
->where('delivery_failures', '<', $threshold)
->update([
'delivery_timeout' => false,
'delivery_next_after' => null,
]);
}
public function down(): void
{
Schema::table('instances', function (Blueprint $table) {
if (Schema::hasColumn('instances', 'nodeinfo_next_after')) {
$table->dropColumn('nodeinfo_next_after');
}
if (Schema::hasColumn('instances', 'nodeinfo_timeout')) {
$table->dropColumn('nodeinfo_timeout');
}
});
}
};

@ -0,0 +1,62 @@
<?php
use App\Jobs\InstancePipeline\FetchNodeinfoPipeline;
use App\Models\Instance;
use App\Services\DeliveryHostService;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Support\Facades\Http;
uses(LazilyRefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| Nodeinfo cooldown must not touch the ActivityPub delivery-backoff columns
|--------------------------------------------------------------------------
|
| FetchNodeinfoPipeline reused delivery_timeout / delivery_next_after, so a
| Nodeinfo failure marked a sub-threshold host unavailable for AP delivery
| for 14h. Nodeinfo now owns nodeinfo_timeout / nodeinfo_next_after; the
| delivery columns are written only by DeliveryHostService.
|
*/
beforeEach(function () {
config(['federation.activitypub.delivery.failure_threshold' => 5]);
Http::fake(['*' => Http::response('', 404)]); // force the Nodeinfo failure branch
DeliveryHostService::reset('sub.example');
});
it('writes nodeinfo cooldown columns and leaves delivery columns untouched on failure', function () {
$instance = new Instance;
$instance->domain = 'sub.example';
$instance->delivery_failures = 1;
$instance->delivery_timeout = false;
$instance->delivery_next_after = null;
$instance->save();
(new FetchNodeinfoPipeline($instance))->handle();
$instance->refresh();
// Nodeinfo cooldown armed on its own columns.
expect((bool) $instance->nodeinfo_timeout)->toBeTrue();
expect($instance->nodeinfo_next_after)->not->toBeNull();
// Delivery-backoff columns untouched by the Nodeinfo crawl.
expect((bool) $instance->delivery_timeout)->toBeFalse();
expect($instance->delivery_next_after)->toBeNull();
});
it('does not report a sub-threshold host unavailable after a nodeinfo failure', function () {
$instance = new Instance;
$instance->domain = 'sub.example';
$instance->delivery_failures = 1; // well under threshold of 5
$instance->delivery_timeout = false;
$instance->delivery_next_after = null;
$instance->save();
(new FetchNodeinfoPipeline($instance))->handle();
// The AP delivery layer must still consider the host available.
expect(DeliveryHostService::isUnavailable('sub.example'))->toBeFalse();
});
Loading…
Cancel
Save