News

Slow Job Alerts and Replica Commit IDs in Laravel 13.40

Published
Slow Job Alerts and Replica Commit IDs in Laravel 13.40 image

The Laravel team released v13.40.0 with callbacks for queue jobs and scheduled tasks that run too long, commit IDs for checking whether a read replica has a write, insertBefore() and insertAfter() for the middleware stack, a strict flag on request input helpers, and a security fix for the Concurrency process driver.

  • Queue::whenJobRunsLongerThan() and Schedule::whenTaskRunsLongerThan()
  • DB::getLastCommitId() and DB::hasCommitId() for read replicas
  • Insert middleware before or after a built-in middleware
  • A strict flag for integer(), float(), boolean(), and string()
  • Signed result output for Concurrency::run() with the process driver
  • Transaction fixes for lost connections and lock wait timeouts

Alerts for Long-Running Jobs and Scheduled Tasks

Jack Bayliss added Queue::whenJobRunsLongerThan(), which runs a callback when a job attempt takes longer than a threshold. Laravel already has DB::whenQueryingForLongerThan() and similar methods for requests and commands, but not for queued jobs:

use Carbon\CarbonInterval;
use Illuminate\Support\Facades\Queue;

Queue::whenJobRunsLongerThan(CarbonInterval::seconds(90), function ($job, $duration) {
    Log::warning("{$job->resolveName()} took {$duration}ms");
});

The callback listens to the JobAttempted event, so it also runs for attempts that fail or are released. A plain number is read as milliseconds, and the duration passed to the callback is in milliseconds.

A second PR adds Schedule::whenTaskRunsLongerThan() for scheduled tasks. It runs after a task finishes or fails, and here a plain number is read as seconds:

use Carbon\CarbonInterval;
use Illuminate\Console\Scheduling\Event;
use Illuminate\Support\Facades\Schedule;

Schedule::whenTaskRunsLongerThan(CarbonInterval::minutes(45), function (Event $task, float $runtime) {
    Log::warning("Scheduled task [{$task->getSummaryForDisplay()}] took {$runtime}s");
});

See #62141 and #62233 for more details.

Commit IDs for Read Replicas

Tobias Petry added methods that tell you whether a read replica has received a specific write. Laravel 13.38 added the PreventStaleDatabaseReads middleware, which sends reads to the write server for a fixed time after a write. With commit IDs, you can check the replica instead of guessing how long the lag is:

use Illuminate\Support\Facades\DB;

// After a write, on the primary
$order = $user->orders()->create($attributes);
$commitId = DB::getLastCommitId();

// Later, on a replica
DB::hasCommitId($commitId);          // Check now
DB::hasCommitId($commitId, wait: 2); // Wait up to two seconds

A mergeCommitIds() method combines several commit IDs into one, so you can wait for all of them at once. The methods work on MySQL with GTIDs enabled, MariaDB with the binary log enabled, and PostgreSQL. Waiting on PostgreSQL requires PostgreSQL 19. Other drivers throw a RuntimeException.

See #62198 for more details.

Insert Middleware Before or After a Built-In Middleware

Jamie York added insertBefore() and insertAfter() to the middleware configuration in bootstrap/app.php. Before this, running your middleware right after one of Laravel's defaults meant declaring the stack yourself:

->withMiddleware(function (Middleware $middleware) {
    $middleware->insertAfter(TrustProxies::class, StripCrossOriginReferer::class);
    $middleware->insertBefore(TrimStrings::class, SanitizeStrings::class);
})

The insertBeforeInGroup() and insertAfterInGroup() methods do the same for a middleware group such as web. An earlier PR in this release also lets replace() and replaceInGroup() accept an array, which is what the new methods use.

See #62230 and #62261 for more details.

Strict Input Casting

Chris Morrell added a strict argument to the string(), str(), boolean(), integer(), and float() methods on requests and other classes that use the InteractsWithData trait. Without it, ?count=abc123 casts to 0, and an array such as ?name[]=chris throws an "Array to string conversion" error. With strict set, invalid values return the default:

// ?count=abc123
$request->integer('count', 5);               // 0
$request->integer('count', 5, strict: true); // 5

In strict mode, boolean() only accepts 1, true, on, yes, 0, false, off, and no.

See #62254 for more details.

Concurrency Process Driver Security Fix

This release fixes a remote code execution issue in Concurrency::run() with the default process driver, reported by @Arr0way. The parent process read a task's result from the child process's output and unserialized it. A task that printed user-controlled text could write a fake result, which the parent then unserialized.

The child process now writes its result in a frame signed with a random key that the parent creates for each task. The parent ignores any frame that the key does not verify. The fix also stops the "Trying to access array offset on null" error that a task printing any output could cause. If your application uses Concurrency::run() with the process driver, update to 13.40.0.

See #62297 for more details.

Database Transaction Fixes

Volodya Kurshudyan fixed two transaction problems:

  • When the connection drops inside a transaction, Laravel now discards the dead PDO and reconnects on the next query. Before, the old PDO still had an open transaction, and every later transaction failed with "There is already an active transaction" until a queue worker or Octane process restarted (#62211)
  • A lock wait timeout or deadlock in a nested transaction now rolls back to that transaction's savepoint. Before, the nested writes were committed if the outer transaction caught the exception (#62248)

Scheduler and Queue Worker Output

Jack Bayliss also sent two changes to what the scheduler and queue worker report:

  • The scheduler now dispatches ScheduledTaskSkipped when an onOneServer() task already ran on another server (#62256)
  • The queue:work command shows RELEASED instead of DONE for jobs released back to the queue, such as by the WithoutOverlapping or RateLimited middleware (#62180)

Release Notes

You can see the complete list of new features and updates below and the diff between 13.39.0 and 13.40.0 on GitHub. The following release notes are directly from the changelog:

v13.40.0

  • [13.x] Fix Paginator::links() return type by @dhrupo in #62226
  • [13.x] Fix updating JSON paths with arrays and collections on MySQL and MariaDB by @xurshudyan in #62225
  • [13.x] Use the write connection in BelongsToMany createOrFirst lookup by @xurshudyan in #62223
  • [13.x] Drop check constraints when dropping columns on SQL Server by @xurshudyan in #62222
  • [13.x] Centralize + improve tests for InteractsWithData by @inxilpro in #62217
  • [13.x] Fix Redis tagged cache flush blocking Redis when flushing many tags by @MohannadElemary in #62215
  • [13.x] Qualify dropped and renamed indexes with the table schema on PostgreSQL by @xurshudyan in #62213
  • [13.x] Reconnect after losing the connection during a transaction by @xurshudyan in #62211
  • [13.x] Add missing PostgreSQL full text languages by @xurshudyan in #62210
  • [13.x] Add Queue::whenJobRunsLongerThan by @jackbayliss in #62141
  • [13.x] Fix named arguments in Cookie::queue() by @xurshudyan in #62246
  • [13.x] Fix ImageFile::default() returning a plain File rule by @Marzaan in #62244
  • [13.x] Fix pagination contracts render() return type by @dhrupo in #62242
  • [13.x] Fix SQL Server ignoring orderBy on update and delete with limit by @xurshudyan in #62239
  • [13.x] Preserve error_get_last() for suppressed errors by @xurshudyan in #62228
  • Prepare reusable development environments for Amp orbs by @DGarbs51 in #62231
  • Allow $middleware->replace() to accept an array of replacements by @ziadoz in #62230
  • [13.x] Show released jobs as released in queue:work output by @jackbayliss in #62180
  • Add $middleware->insertBefore() and $middleware->insertAfter() helper methods by @ziadoz in #62261
  • [13.x] Reconnect the Cloud log socket after a failed write by @jamesdangercarpenter in #62258
  • [13.x] Dispatch ScheduledTaskSkipped when a task already ran on another server by @jackbayliss in #62256
  • [13.x] Fix forceIndex() generating invalid SQL on SQLite for PRIMARY and multiple indexes by @tharlei in #62255
  • [13.x] Add Schedule::whenTaskRunsLongerThan by @jackbayliss in #62233
  • [13.x] Roll back to savepoint on concurrency errors in nested transactions by @xurshudyan in #62248
  • [13.x] Fix toResourceCollection() on filtered collections by @xurshudyan in #62266
  • Fix lazily loaded images not being freed until garbage collection by @tamc in #62265
  • Bump stefanzweifel/git-auto-commit-action from 7.2.0 to 7.2.1 in the github-actions group by @dependabot[bot] in #62288
  • KeyGenerateCommand: improve help message about '--show' by @valerio-bozzolan in #62286
  • [13.x] Record failed batch jobs when the model is missing by @xurshudyan in #62280
  • [13.x] Fix broadcasting to multiple encrypted Pusher channels by @xurshudyan in #62279
  • [13.x] Fix Str::remove() rejecting a Traversable subject by @Amirhf1 in #62278
  • [13.x] Fix Str::substrCount() treating offset and length as bytes by @Amirhf1 in #62277
  • [13.x] Fix withExists() and loadExists() on SQL Server by @xurshudyan in #62273
  • [13.x] Fix cursorPaginate() columns on union queries with multiple orders by @xurshudyan in #62272
  • [13.x] Fix @param types wider than the native parameter type by @dhrupo in #62271
  • [13.x] Detect QueryList.TerminateAll() as lost connection by @jackbayliss in #62270
  • [13.x] Add a strict flag to InteractsWithData by @inxilpro in #62254
  • [13.x] database fundamentals to know if a read connection has catched up to a write connection by @tpetry in #62198
  • [13.x] Fix CommandSignatureTest by @jackbayliss in #62295
  • [13.x] Fix JSON:API resource collections wrapping plain objects by @lazerg in #62299
  • [13.x] Fix: Authenticate the Concurrency Process Result Envelope to Prevent Task Output Injection by @Arr0way in #62297
Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

laravelcloud logo
Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Cloud

The latest

View all →
Flock: A Free, Native Herd Alternative for Laravel image

Flock: A Free, Native Herd Alternative for Laravel

Read article
Live Props for Real-Time Updates in Inertia.js v3.11 image

Live Props for Real-Time Updates in Inertia.js v3.11

Read article
Cooldown Dependency Policy now in Composer 2.11 image

Cooldown Dependency Policy now in Composer 2.11

Read article
Email Sending and Testing for Laravel in One Account image

Email Sending and Testing for Laravel in One Account

Read article
Decide with Jev: Tie Every AI Check to the Exact Draft image

Decide with Jev: Tie Every AI Check to the Exact Draft

Read article
PIE 1.5 Changes PHP Extension Installs in CI image

PIE 1.5 Changes PHP Extension Installs in CI

Read article