Laravel Queues In Action 2nd Mohamed Said

buzinabaza2p 8 views 81 slides May 14, 2025
Slide 1
Slide 1 of 81
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76
Slide 77
77
Slide 78
78
Slide 79
79
Slide 80
80
Slide 81
81

About This Presentation

Laravel Queues In Action 2nd Mohamed Said
Laravel Queues In Action 2nd Mohamed Said
Laravel Queues In Action 2nd Mohamed Said


Slide Content

Laravel Queues In Action 2nd Mohamed Said
download
https://ebookbell.com/product/laravel-queues-in-action-2nd-
mohamed-said-49033536
Explore and download more ebooks at ebookbell.com

Here are some recommended products that we believe you will be
interested in. You can click the link to download.
Laravel 11 Documentation 11th Edition Taylor Otwell
https://ebookbell.com/product/laravel-11-documentation-11th-edition-
taylor-otwell-56620832
Laravel Up Running 3rd Edition Matt Stauffer
https://ebookbell.com/product/laravel-up-running-3rd-edition-matt-
stauffer-53638256
Laravel Starter Shawn Mccool
https://ebookbell.com/product/laravel-starter-shawn-mccool-2624840
Laravel Matt Stauffer Matt Stauffer
https://ebookbell.com/product/laravel-matt-stauffer-matt-
stauffer-30066442

Laravel Application Development Blueprints Arda Kilidagi Halil Ibrahim
Yilmaz
https://ebookbell.com/product/laravel-application-development-
blueprints-arda-kilidagi-halil-ibrahim-yilmaz-4656440
Laravel Up Running A Framework For Building Modern Php Apps Matt
Stauffer
https://ebookbell.com/product/laravel-up-running-a-framework-for-
building-modern-php-apps-matt-stauffer-5473776
Laravel 5 Essentials Explore The Fundamentals Of Laravel One Of The
Most Expressive And Robust Php Frameworks Available Martin Bean
https://ebookbell.com/product/laravel-5-essentials-explore-the-
fundamentals-of-laravel-one-of-the-most-expressive-and-robust-php-
frameworks-available-martin-bean-5473886
Laravel 5x Cookbook Alfred Nutile
https://ebookbell.com/product/laravel-5x-cookbook-alfred-
nutile-7029938
Laravel Up Running 2nd Edition Matt Stauffer
https://ebookbell.com/product/laravel-up-running-2nd-edition-matt-
stauffer-9997518

Contents
Preface 4 ....................................................................................................................
Introduction to Queues 5 ..............................................................................................
The Queue System 9 .............................................................................................
Queues in Laravel 11 .............................................................................................
The Advantages of Using Queues 16 .......................................................................
Cookbook 20 ...............................................................................................................
Sending Email Verification Messages 22 ..................................................................
High Priority Jobs 28 .............................................................................................
Retrying Failed Jobs 32 ..........................................................................................
Configuring Automatic Retries 36 ...........................................................................
Canceling Abandoned Orders 41 .............................................................................
Sending Webhooks 45 ...........................................................................................
Provisioning a Forge Server 49 ................................................................................
Canceling a Conference 54 .....................................................................................
Preventing a Double Refund 60 ..............................................................................
Preventing a Double Refund With Unique Jobs 66 .....................................................
Bulk Refunding Invoices 72 ....................................................................................
Selling Conference Tickets 83 .................................................................................
Spike Detection 89 ................................................................................................
Processing Uploaded Videos 93 ..............................................................................
Sending Monthly Invoices 97 ..................................................................................
Controlling The Rate of Job Execution 101 ...............................................................
Dealing With API Rate Limits 105 ............................................................................
Dealing With an Unstable Service 109 .....................................................................
Provisioning a Serverless Database 115 ...................................................................
Generating Complex Reports 121 ...........................................................................
FIFO Queues 126 ..................................................................................................
Dispatching Event Listeners to Queue 132 ...............................................................
Sending Notifications in the Queue 136 ...................................................................

Dynamic Queue Consumption 138 ..........................................................................
Customizing The Job Payload 143 ...........................................................................
A Guide to Running and Managing Queues 147 ...............................................................
How Laravel Dispatches Jobs 148 ............................................................................
Creation of The Job Payload 155 .............................................................................
How Workers Work 160 ........................................................................................
Choosing the Right Machine Configuration 168 ........................................................
Keeping the Workers Running 174 ..........................................................................
Scalability of The Queue System 177 .......................................................................
Scaling With Laravel Horizon 182 ............................................................................
Choosing The Right Queue Driver 187 .....................................................................
Handling Queues on Deployments 195 ....................................................................
Designing Reliable Queued Jobs 197 .......................................................................
Managing The State 202 ........................................................................................
Dealing With Failure 207 ........................................................................................
Dispatching Large Batches 211 ...............................................................................
Reference 214 .............................................................................................................
Worker Configurations 214 ....................................................................................
Job Configurations 218 ..........................................................................................
Connection Configurations 221 ..............................................................................
Horizon Configurations 223 ....................................................................................
Built-in Middleware 225 ........................................................................................
Job Interfaces 228 .................................................................................................

4
Preface
Hey, welcome to Laravel Queues in Action! I'm glad you're reading this book and excited to
join you in exploring the many ways we can use the queue system to enhance our
applications.
Over the years, I worked on projects that heavily relied on asynchronous task execution.
Also, during my time at Laravel, I worked with hundreds of developers to solve problems
that involved the queue system. Along the way, I contributed to enhancing the system by
adding new features, fixing bugs, and improving performance.
While the Laravel community was growing, we collectively discovered best practices for
working with the queue system. This knowledge was shared in separate blog posts here and
there. But there still wasn't a comprehensive guide on utilizing its power and dealing with
common problems in the community.
It was with this in mind that I decided to write this book in 2020. Fast forward to 2022,
several Laravel releases came out, and more usage patterns were discovered. These new
releases introduced several enhancements to the system that dealt with many performance
and developer experience issues.
In this second edition, I think that the book is now about giving you the information you
need to make educated decisions about how to best use the queue system in your projects. I
have done my best to make the book insightful to first-time queue system explorers and
long-time professionals.
I hope that Laravel Queues in Action proves helpful!

5
CHAPTER 1
Introduction to Queues
A web application is a series of procedures that pass data around. It takes data in, performs
several operations, and passes it back out. That makes a web application a form of data
pipeline. The term pipeline is borrowed from industry in which pipes transport substances
from one place to another. In the context of web applications, each pipe—sometimes
referred to as stage—in a pipeline performs an operation that validates, transforms, stores,
or transports the data. Other stages may not touch the data, but they take action based on
it.
Figure 1-1. A data pipeline
Like an industrial pipeline, the data needs to pass through one pipe to move to the next, and
each pipe has a resistance coefficient. The higher the coefficient, the slower the data will
move in a pipe. Therefore, adding more pipes with high resistance will result in an overall
delay in passing the data out of the final pipe.
Take a look at this example controller action:
public function store(Request $request)
{
$validated = $this->validate($request, [
'email' => 'email'
]);

6
$user = User::create($validated);
Mail::to($user)->send( new Welcome());
return UserResource::make($user);
}
In this data pipeline, the data is passed from the request to the validation stage, the storage
stage, the mail sending stage, and the transformation stage before it's passed out to the
client making the request. Each stage's resistance coefficient dictates how fast the data will
pass through. The slower the operation, the longer it takes for the response to be returned
to the client.
But unlike industrial pipelines, a single web application instance can only handle one request
at any time. If more requests come, they will have to wait until the current request is passed
out of the final pipe. That means slow operations not only delay the response of one request
but also delay handling other requests. For example, if the machine that hosts your web
application can run fifty instances of the web application and all fifty slots are occupied, new
requests will have to wait.
For a business to be able to handle more traffic, it has three options:
Buy more machines (called scaling out).1.
Buy more computing resources for the existing machines (called scaling up).2.
Task software engineers to decrease the resistance coefficient of all the stages of3.
handling a request.
Sometimes throwing money at the problem–buying more computing resources–is more
efficient. Other times putting more software engineering efforts is more effecient.
Decreasing the resistance can be achieved in various ways:
Code performance optimization (Figure 1-2).1.
Concurrent processing; running several stages in parallel (Figure 1-3).2.
Asynchronous execution; removing some stages from the main pipeline and putting it3.
into another pipeline (Figure 1-4).

7
Figure 1-2. The 2nd stage was optimized for performance
Figure 1-3. The 2nd & 3rd stages were executed concurrently
Figure 1-4. The 2nd & 3rd stages were moved to a different pipeline
Laravel is a web framework that ships with many tools that help us monitor and enhance the
performance of our code. It also provides us with tools we can use for concurrent processing
and asynchronous execution.

8
Concurrent Execution
For stages that need to complete before sending the response back to the user, we may
install Laravel Octane and use the Swoole client. Doing so will allow us to split an operation
into several smaller ones, execute them concurrently, and wait for the result before sending
it to the user.
This pattern is called fan-out, fan-in. Fanning out is the process of distributing work on
multiple processors, and fanning in is combining the results of those processes into one set
and passing it to the next stage of the pipeline.
Concurrent execution in PHP is not supported out of the box. However, the Swoole (and its
fork Open Swoole) extension allows us to use multiprocessing to send commands from our
main process (pipeline) to one or more worker processes and wait for the results. Laravel
Octane utilizes this and abstracts it under the concurrently() method:
public function store(Request $request)
{
$request->user()->can( 'load_entities');
[$users, $servers] = Octane::concurrently([
fn () => User::all(),
fn () => Supplier::all(),
]);
return [$users, $servers];
}
In this example, the authorization stage will ensure the current logged-in user is allowed to
load_entities, then using Octane::concurrently() , we can fan out the tasks of
loading users and suppliers to two worker processes to work on them in parallel. Once the
results from the two processes come back, they will be moved to the transformation stage
before responding to the web client.
Octane's concurrency model helps us run several stages of our program simultaneously and
shorten the time needed to handle the request. But what if we can move some of the stages
completely from the pipeline to a different pipeline? That way, we can remove all the work
that doesn't need to happen synchronously–while handling the request–and execute it
asynchronously in the background.
Take another look at this example:

9
$user = User::create($validated);
Mail::to($user)->send( new Welcome());
return UserResource::make($user);
We don't have to pass the request through the mail-sending stage before it reaches the
transformation stage. Instead, we can pass the request directly from the storage stage to the
transformation stage and move the mail sending stage to an asynchronous pipeline to be
done later.
That's what the queue system that ships with Laravel is for.
The Queue System
A user interface is how a human interacts with a program, and a programmable interface
(API) is how a program interacts with another program. A queue system offers a
programmable interface that programs use to communicate asynchronously, not in real-
time. One program offloads work to another to be done later.
When you send a message through Slack, it will be stored in a database until the receiver(s)
can check it out. That's the case for any form of asynchronous communication; messages
need to be stored in a place until a receiver is available to read them.
The simplest form of storing several items in PHP is arrays. So let's see how a queue would
look when its messages are stored in an array:
$queue = [
'Send mail to user #1' ,
'Send mail to user #2'
];
This queue contains two messages. We can enqueue a new message, and it'll be added to
the end of the queue:
enqueue('Send mail to user #3' );
$queue = [
'Send mail to user #1' ,

10
'Send mail to user #2' ,
'Send mail to user #3'
];
We can also dequeue a message, and it'll be removed from the beginning of the queue:
$message = dequeue(); // == Send mail to user #1
$queue = [
'Send mail to user #2' ,
'Send mail to user #3'
];
Notice: If you ever heard the term "first-in-first-out (FIFO)" and didn't understand
what it means, now you know it means the first message in the queue is the first
message that gets processed.
Now a message is a call-to-action trigger; its body contains a string, the receiver interprets
this string, and the call to action is extracted. In a queue system, the receiver is called a
worker. Which is a computer program that constantly dequeues messages from a queue,
extracts the call-to-action, and executes it.
Figure 1-5 shows a logical view of how the queue system works. Web application instances
enqueue jobs in the queue store while workers keep checking it for messages. Once a
worker detects a message is present, it dequeues and processes that message.

11
Figure 1-5. Workers in action
As you can see, a web application doesn't wait for the message to be processed; it's one-way
communication. It just sends it to the queue and continues handling more client requests.
On the other hand, workers don't care where the message is coming from. Their only job is
to process a message and move to the next.
Queues in Laravel
Laravel ships with a powerful queue system right out of the box. It supports multiple drivers
for storing messages:
Database
Beanstalkd
Redis
Amazon SQS
Enqueuing messages in Laravel can be done in several ways, and the most basic method is
using the Queue facade:
use Illuminate\Support\Facades\Queue;
Queue::pushRaw('Send mail to user #1' );
If you're using the database queue driver, calling pushRaw() will add a new row in the jobs

12
table with the message "Send mail to user #1" stored in the payload field.
Enqueuing raw string messages like this is not very useful. Workers don't know how to
interpret plain English and extract the action that needs to be triggered. For that reason,
Laravel allows us to enqueue class instances:
use Illuminate\Support\Facades\Queue;
use App\Jobs\SendWelcomeEmail;
Queue::push(
new SendWelcomeEmail(1)
);
Notice: Laravel uses the term "push" instead of "enqueue", and "pop" instead of
"dequeue".
When you enqueue an object, Laravel will serialize it and build a string payload for the
message body. When workers dequeue that message later, they will be able to extract the
object and call the proper method to trigger the action.
Laravel refers to a message that contains a class instance as a "Job". To create a new job in
your application, you may run this artisan command:
php artisan make:job SendWelcomeEmail
This command will create a SendWelcomeEmail job inside the app/Jobs directory. That job
will look like this:
namespace App\Jobs;
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public function __construct()
{

13
}
public function handle()
{
// Execute the job logic.
}
}
When a worker dequeues this job, it will execute the handle() method. Inside that method,
you should put all your business logic.
Notice: Starting Laravel 8.0, you can use __invoke instead of handle for the method
name.
Another way of sending messages to the queue that workers can interpret is by using a
closure (an anonymous function):
use Illuminate\Support\Facades\Queue;
Queue::push(function() use ($user){
Mail::to($user)->send( new Welcome());
});
You can even use arrow functions:
use Illuminate\Support\Facades\Queue;
Queue::push(
fn() => Mail::to($user)->send( new Welcome())
);
Behind the scenes, Laravel will serialize the closure by converting it into a string form. Then,
when the worker picks it up, it can convert it back to its closure form and invoke it.
The Command Bus
Laravel ships with a command bus that can be used to dispatch jobs to the queue.
Dispatching through the command bus allows us to use several functionalities I will show

14
you later.
Throughout this book, we will use the command bus to dispatch our jobs instead of the
Queue::push() method.
Here's an example of using the dispatch() helper, which uses the command bus under the
hood:
use App\Jobs\SendWelcomeEmail;
dispatch(
new SendWelcomeEmail(1)
);
Or you can use the Bus facade:
use Illuminate\Support\Facades\Bus;
use App\Jobs\SendWelcomeEmail;
Bus::dispatch(
new SendWelcomeEmail(1)
);
You can also use the dispatch() static method on the job class:
use App\Jobs\SendWelcomeEmail;
SendWelcomeEmail::dispatch( 1);
Notice: Arguments passed to the static dispatch() method will be transferred to
the job instance automatically.
Starting A Worker
To start a worker, you need to run the following artisan command:
php artisan queue:work

15
This command will run a program on your machine which will bootstrap an instance of the
Laravel application and keep checking the queue for jobs to process.
$app = require_once __DIR__.'/bootstrap/app.php' ;
while (true) {
$job = $app->dequeue();
$app->process($job);
}
Similar to the HTTP (web application) pipeline, a worker can only handle a single job at any
given time. To dequeue jobs faster, you'll need to start multiple workers to process jobs in
parallel. We will look into managing workers later in this book.
Now the queue:work program that ships with Laravel loads a single instance of your
application and uses it to process all jobs. That's different from how a standard web request
is handled. Handling web requests in PHP is done by bootstrapping a fresh application
instance exclusively for each request and terminating that instance after the request is
handled (Figure 1-6).
Figure 1-6. Request handling vs. job handling
Bootstrapping an application is a stage with a high resistance coefficient. Therefore, doing it
only once in the lifetime of a queue worker has a considerable performance benefit as it will
allow the worker to handle more jobs per a given time interval.
However, this benefit comes at a cost. When the same application instance is used, it
becomes the software engineer's job to take care of managing the memory. A memory leak
somewhere in the code will cause the process to consume all available memory in the

16
machine and eventually crash it entirely. That's why PHP starts a fresh instance to handle a
web request. To free engineers from managing memory.
Notice: Laravel knows the cost of bootstrapping the application for every web
request. That's why it gives engineers the option to use Laravel Octane. Web requests
are handled by Octane similar to how queued jobs are handled by a queue worker,
using the same application instance.
Memory management may seem like a highly complex topic, and it is indeed if you are new
to the concept. Luckily, Laravel puts some safeguards in place to prevent workers from
eating all machine memory. And in the following chapters of this book, we will discuss the
topic in detail and go through the best practices to ensure memory consumption is kept in
check.
Another side effect of bootstrapping the application once is that data may leak between jobs
if you are not careful. This will cause bugs that are very hard to debug as they will only
happen if a worker dequeues jobs that come in a specific order, which is very hard to
replicate. We will talk about that in detail in the upcoming chapters as well.
The Advantages of Using Queues
Every virtual private server, aka VPS, has a limit for the concurrent number of requests it can
handle. Even serverless offerings like AWS Lambda has this limit. Therefore, you need to
handle requests faster to increase the throughput of your VPS or Lambda function without
scaling your infrastructure. We've discussed this earlier and mentioned that moving slow
stages to the queue will help us handle requests faster. Responding faster to the user is also
a significant enhancement to the user experience.
But faster request handling is not the only reason for using queues. Another significant
benefit of using queues is decoupling different stages of your application logic. Let's take a
look at an example:
public function store(Request $request)
{
$validated = $request->validate(...);
$order = Order::create($validated);
Webhooks::each(function($webhook) {

17
$webhook->send($order);
});
return OrderResource::make($order);
}
In this example, the request goes through several stages:
Validation stage.1.
Storage stage.2.
Webhook sending stage.3.
Transformation stage.4.
The validation, storage, and transformation stages are essential for handling this web
request. However, can we say the same about the webhook sending stage? The answer is
no. We can definitely send the webhooks later after responding to the user.
Sending those webhooks while handling the request could be a swift operation, and
removing this stage won't affect the throughput or user experience that much. But, What if
one of the webhook receivers went down temporarily? Should we abort the entire request
because one receiver failed? Or should we send that failed webhook again? If we retry, how
many times? If we abort the request and throw an error, what happens when the user sends
the same request again? Some receivers have already received the webhook; are we going
to send them the same webhook again?
There are a lot of questions here. And the simple answer is decoupling. In a loosely coupled
application, different parts can be handled in various manners without affecting other parts.
Decoupling the webhook sending stage from the request handling process allows us to
monitor each webhook sent and retry when needed.
Here's how we can rewrite this controller action:
public function store(Request $request)
{
$validated = $request->validate(...);
$order = Order::create($validated);
Webhooks::each(function($webhook) {
SendWebhook::dispatch($webhook, $order);
});
return OrderResource::make($order);

18
}
We've swapped the actual webhook sending with dispatching a job that will handle sending
the webhook later. Inside the queue, we can configure the job for each webhook to
automatically retry several times if the receiver fails. We can also configure how much time
we should wait before every retry. We will learn how to do that later in this book.
Concurrency
To efficiently deliver all webhooks as soon as possible, we can utilize the concurrency
offered by Laravel Octane. However, this comes with the task of memory management since
the requests will be handled in the same memory space as we explained earlier in this
chapter.
Using Laravel queues, we can achieve concurrency by running multiple workers to process
jobs from the queue. That way, you can leave the request handling process of the
application to run in the usual manner of handling each request via a separate process
without sharing memory.
Rate Limiting
Another benefit we get from decoupling is that we can control the rate a particular part of
the application is executed. In the example above, we can control the rate at which webhook
sending jobs are executed so we don't hit the receiver's rate limit. When integrating with a
third-party API that we have no control over, this is crucial.
Laravel's rate limiting capabilities allow us to easily control the number of times a particular
job is executed and the number of times several instances of a job can be executed
concurrently. We'll learn about that in the upcoming chapters.
Scheduling
Sometimes you may wish to delay a stage in your web application to be performed after a
few seconds. For example, you may want to delay delivering a webhook to allow your
database replica instances to read newly written records before a third-party application can
read them. To achieve that, you may do something like this:
$order = Order::create($validated);
sleep(2);

19
$webhook->send($order);
The sleep(2) stage here will block the execution of the script for 2 seconds before moving
to the webhook sending stage. This is obviously something you'll want to avoid in a web
application.
Using queues, you can schedule a specific job to be executed after a specific period:
$order = Order::create($validated);
SendWebhook::dispatch($webhook, $order)->delay( 2);
Using delay(2) while dispatching the job to the queue instructs Laravel to delay processing
this job for 2 seconds. In other words, workers will skip this job for 2 seconds before picking
it up. During those 2 seconds, the worker will pick up other jobs in the queue so it won't
remain idle.
Prioritization
A computer system is limited by the number of tasks of a particular type it can handle during
a given period. Take sending notification emails, for example; if your system can send 1000
emails per hour, you'd want to ensure the most important notifications are sent while less
important ones are delayed for later.
Using a queue system, you can give a particular queue a higher priority than others. Workers
will always process jobs from that queue before all other queues.

20
CHAPTER 2
Cookbook
In chapter 1, we used the concepts of industrial pipelines to explain how a Laravel
application handles web requests. And we learned how to increase the request throughput
by removing some stages from the web pipeline to the queue pipeline.
In this chapter, we will walk through several real-world challenges with solutions that run in
production. I met these challenges while building products at Laravel and Foodics, and
others collected while supporting the framework users for the past six years.
Before we can jump into these challenges, let's learn how to configure the queue in a fresh
Laravel application.
Configuring the Queue
Queued jobs need to be stored somewhere, and Laravel ships with several storage drivers.
Each of these drivers has its pros and cons, and we will discover those in detail later in this
book. However, the database driver will be the easiest to configure on a local development
machine. That's why we'll use it for the examples in this book.
Besides being easy to set up, the database driver gives us high visibility. We can easily open
our favorite GUI tool for managing databases and explore the jobs in the queue. We can also
delete jobs and make tweaks to test things out.
All we need to get started is to create a database and configure the database connection in
the config/database.php configuration file.
After that, we need to create the migration that creates the jobs database table:
php artisan queue:table
Running this artisan command will add a migration inside the database/migrations
directory that creates a jobs table. The code inside the migration will look like this:

21
Schema::create('jobs', function (Blueprint $table) {
$table->bigIncrements( 'id');
$table->string('queue')->index();
$table->longText( 'payload');
$table->unsignedTinyInteger( 'attempts');
$table->unsignedInteger( 'reserved_at')->nullable();
$table->unsignedInteger( 'available_at');
$table->unsignedInteger( 'created_at');
});
The queue column will hold the name of the queue a job is stored in, and yes, we can
dispatch jobs to multiple queues in Laravel. We'll explore the benefits of this later.
The payload column will hold the body of the job, which is the string representation of the
job object, along with other essential configuration attributes. Like the allowed number of
attempts, expiration, timeout, and other ones we will explore later.
The attempts column will hold the number of times the job has been attempted.
The reserved_at column will hold the timestamp of when a worker picked up this job.
The available_at column will hold the timestamp of when workers are allowed to pick
this job up. Sometimes it's helpful to delay processing some jobs.
The created_at column will hold the timestamp of when the job was dispatched to the
queue.
Now that we added the migration and understood what each column is for, let's run the
migrate command:
php artisan migrate
If we check the database after this step, we will find the jobs table created.
Finally, let's configure Laravel to use the database queue driver. We will edit the
QUEUE_CONNECTION environment variable inside the .env file and set it to database:
QUEUE_CONNECTION=database
Our Laravel application is now ready to store jobs we dispatch from anywhere in the code.

22
So let's jump into our first challenge.
Sending Email Verification Messages
When it comes to onboarding users to an application, providing the least amount of friction
is critical. Unfortunately, some friction is necessary, like making sure the user-provided email
address is theirs.
To verify an email address, the application will have to send an email with a link. Then, when
the user clicks on that link, we'll know they have access to that email inbox.
The sign-up controller action may look like this:
function store()
{
$this->validate(...);
$user = User::create([
...,
'email_verified' => false
]);
Mail::to($user)->send(...);
return redirect('/login?pending_verification=true' );
}
Here are the stages of this action:
Validating the user input.1.
Adding the user to the database with email_verified = false.2.
Sending the verification email message.3.
Redirecting the user to the /login route.4.
In the login view, we check the pending_verification query parameter and display a
message asking the user to check their inbox and verify the email address before they can
log in.
Sending the email message might take several seconds as our server will have to
communicate with the mailing service provider and wait for a response. The user will have to
wait until this communication occurs before they get redirected to the /login route and

23
see a meaningful message.
Forcing the user to wait several seconds during their first interaction with the application
isn't the best user experience as it might give them the notion that the application is slow.
It'll be great if we can redirect the user to the login screen immediately and send the email
message in the background (asynchronously).
Creating and Dispatching the Job
To send the email message via the queue, we will create a SendVerificationMessage job:
php artisan make:job SendVerificationMessage
The job will be created in app/Jobs/SendVerificationMessage.php , and will look like
this:
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendVerificationMessage implements ShouldQueue
{
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
public function __construct()
{
//
}
public function handle()
{
//
}
}
To send the email from within the job, we will move the call to Mail::send() from the
controller action to the handle() method of the job:

24
public function __construct(
private User $user
) {}
function handle()
{
Mail::to($this->user)->send(...);
}
Now back to the controller action, let's dispatch the job using the dispatch() static
method:
function store()
{
$this->validate(...);
$user = User::create([
...,
'email_verified' => false
]);
SendVerificationMessage::dispatch($user);
return redirect('/login?pending_verification=true' );
}
If we run this controller action, the command bus will send the
SendVerificationMessage job to the queue.
When we check the jobs table, we are will see the following record:
{
"id": 1,
"queue": "default",
"payload": "{...}",
"attempts": 0,
"reserved_at": null,
"available_at": 1591425946,
"created_at": 1591425946
}

25
The payload field contains the queued job we just sent, and it's a JSON string that contains
information about our job and a serialized version of the SendVerificationMessage
instance we sent. Here's how it may look:
{
"uuid":"67b31b39-26df-4b39-b2db-c4fb78088fd1" ,
"displayName":"App\\Jobs\\SendVerificationMessage" ,
"job":"Illuminate\\Queue\\CallQueuedHandler@call" ,
"maxTries":null,
"maxExceptions":null,
"failOnTimeout":false,
"backoff":null,
"timeout":null,
"retryUntil":null,
"data":{
"commandName":"App\\Jobs\\SendVerificationMessage" ,
"command":"O:32:\"App\\Jobs\\SendVerificationMessage\":1:{s:4:\"user\";O:45:\"Illuminate
\\Contracts\\Database\\ModelIdentifier\":4:{s:5:\"class\";s:15:\"App\\Models\\User\";s:2
:\"id\";i:1;s:9:\"relations\";a:0:{}s:10:\"connection\";s:5:\"mysql\";}}"
}
}
In the following chapters, we'll look closely at each attribute in the job payload. But, for now,
notice the value of the data.command attribute. This is how the
SendVerificationMessage job looks after it's serialized.
Starting a Worker
At this point, we managed to dispatch the job to the database queue and redirect the user to
the /login route so they don't have to wait.
Meanwhile, the job is sitting in the jobs database table along with other jobs that resulted
from different users signing up.
To start processing those jobs, we need to start a worker:
php artisan queue:work
Once we run this command, we will see information about the jobs being processed by the
worker inside our terminal window:

26
[2020-03-06 06:57:14][1] Processing: App\Jobs\SendVerificationMessage
[2020-03-06 06:57:16][1] Processed: App\Jobs\SendVerificationMessage
[2020-03-06 06:57:16][2] Processing: App\Jobs\SendVerificationMessage
[2020-03-06 06:57:17][2] Processed: App\Jobs\SendVerificationMessage
[2020-03-06 06:57:17][3] Processing: App\Jobs\SendVerificationMessage
[2020-03-06 06:57:19][3] Processed: App\Jobs\SendVerificationMessage
The job with ID = 1 was processed first, then the one with ID = 2, then ID = 3 and so
on...
Once the worker picks a job up, it will execute the handle() method, which will run our
code for sending the email message.
Running Jobs Concurrently
If the queue is filled with verification messages that need to be sent, a new user signing up
will have to wait for a long time before they receive their verification message. That message
won't be sent until all the previous messages in the queue are processed.
To solve this problem, we will have to process those jobs in parallel.
Each worker can only process a single job at any given time. To process multiple jobs in
parallel, we'll need to start more workers:
php artisan queue:work
php artisan queue:work
Notice: To run multiple workers on our local machine, we must open multiple
terminal windows—one for each worker.

27
Figure 2-1. Workers processing jobs in parallel
As we can see in this simplified sequence diagram, while the web application dispatches new
jobs to the queue, workers are picking up and processing them. Two workers can pick two
jobs and process them at the same time. Resulting in consuming the queue faster.
You may think that since the two workers are running in parallel, then there's a chance that
both of them will pick up the same job resulting in sending the same verification email to the
same user twice. But that's not going to happen. All queue storage drivers use atomic locks
to prevent multiple workers from picking the same job.
Let's look at the query executed by the database queue driver when using MySQL:
SELECT * FROM `jobs`
WHERE ...
ORDER BY `id` ASC
FOR UPDATE SKIP LOCKED
LIMIT 1
The last line in the query has the FOR UPDATE instruction, which tells MySQL to take an
exclusive lock on the returned row. It also has SKIP LOCKED, which tells MySQL to skip any
locked row and exclude it from the query. That way, a lock will be put on every job
dequeued by a worker. When another worker dequeues, this locked row won't be visible to
it.

28
High Priority Jobs
In the previous challenge, we learned that to process jobs faster, we need to start more
workers.
But even with multiple workers running, if the queue is filled with
SendVerificationMessage jobs and we push a ProcessPayment job, the workers won't
pick that job up until all previous SendVerificationMessage jobs are processed (Figure
2-2).
Figure 2-2. The payment job is at the back of the queue
Sending verification messages can wait, but processing payments has a higher priority. So we
need to tell our workers to process ProcessPayment jobs before others.
Your first thought might be to dispatch payment processing jobs to the beginning of the
queue rather than to the back of the queue. That way, workers will always pick these jobs up
if they exist before processing any other kinds of jobs (Figure 2-3). However, this will risk
having all SendVerificationMessage jobs un-consumable by workers if there are always
ProcessPayment in the queue. So we need something more flexible.

29
Figure 2-3. Dispatching payment jobs to the beginning of the queue
Laravel allows us to dispatch jobs to multiple queues. So, we can push all ProcessPayment
jobs to a particular queue, configure our workers to consume jobs from that queue—besides
the default queue—and finally give that queue a higher priority.
Dispatching to Multiple Queues
If we take a look at the database queue connection in the config/queue.php file, we'll see
the following:
return [
'connections' => [
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
],
]
];
Having queue = default instructs Laravel to push jobs to a queue named default and
workers to process jobs from that queue.
To enqueue our ProcessPayment jobs in a separate payments queue, we'll need to instruct
Laravel to do so while dispatching the job explicitly:
ProcessPayment::dispatch($payment)->onQueue( 'payments');

30
Alternatively, we can set a $queue public property in the job class and set it to payments:
namespace App\Jobs;
class ProcessPayment implements ShouldQueue
{
public $queue = 'payments';
}
Notice: By defining the $queue public property, we no longer need to call onQueue()
when dispatching the job. Each time this job is dispatched, Laravel will send it to the
payments queue automatically.
Processing Jobs from Multiple Queues
At this point, all our ProcessPayment jobs are stored in the payments queue while all the
other jobs are in the default queue. And in the previous challenge, we had a few workers
running and processing jobs from the default queue by default.
Let's also instruct our workers to process jobs from the payments queue. We'll do this by
terminating those workers and starting new ones with the --queue option configured:
php artisan queue:work --queue=payments,default
This will instruct the worker to pop—or dequeue—jobs from the payments and default
queues, with the payments queue having a higher priority (Figure 2-4). Jobs from the
default queue will not be processed until the payments queue is cleared.

31
Figure 2-4. Dispatching to multiple queues
Using Separate Workers for Different Queues
Using --queue=payments,default in the queue:work command allowed us to configure
the queue priority for each worker. However, if the payments queue is always filled with
jobs, jobs from queues with lower priority may not run at all.
In most production cases I've seen, this was never a problem. However, if we want to ensure
that there will always be a worker processing jobs from each queue, we can configure a
separate worker for each one:
php artisan queue:work --queue=payments
php artisan queue:work --queue=default
Having these two workers running ensures that there's always a worker processing jobs from
the payments queue while another worker is processing jobs from the default queue.
One thing with this, though, is if the default queue is empty while the payments queue is
full, the second worker will stay idle. So it's better if we instruct the workers to process jobs
from the other queue if their main queue is empty.
Notice: Running workers consume memory and CPU cycles. Therefore, we should
always try to use all available workers most efficiently.
To do this, we'll start our workers with these configurations:
php artisan queue:work --queue=payments,default

32
php artisan queue:work --queue=default,payments
Now, the first worker will consume jobs from the payments queue as long as it has jobs; if
not, it'll consume jobs from the default queue. The second worker will consume jobs from
the default queue if it's busy; otherwise, it'll start looking at the payments queue.
Figure 2-5. Running multiple workers
Figure 2-5 shows how worker two will not remain idle if the default queue is empty.
Instead, it will start consuming jobs from the busy payments queue. No wasted resources.
Retrying Failed Jobs
We're still looking into configuring email verification jobs. The handle() method of the
SendVerificationMessage job currently looks like this:
function handle()
{
Mail::to($this->user)->send(...);
}
This simple job's success depends on the network connectivity, availability of the mail
service provider, and its acceptance to the mail sending request. Some of these things we
have some control over (network connectivity, sending valid payload, paying the bills, etc...),
others not (availability of the mail service). In all cases, we need to plan for when a job fails
as much as when it succeeds.
When a failure happens inside one of the stages of handling a web request, the application
will throw an exception, and the user will get a message indicating the request failed. They

33
can retry the request later or contact customer support. We can also configure logging to get
notified when one of our users faces an error. For queued jobs that run asynchronously,
though, handling failure can be a bit tricky. Here's what Laravel does by default when a job
fails:
Deletes the job from the queue.1.
Reports the failure in the terminal window running the queue:work command.2.
Dispatches a JobFailed event.3.
Stores the job information in a failed jobs store.4.
This failed jobs store is known in the computer science world as Dead-letter Queue. Its
primary purpose is logging and debugging unprocessed jobs. Laravel stores a record of failed
jobs in a failed_jobs database table. Inside this table, a record may look like this:
{
"id": 300,
"uuid": "67b31b39-26df-4b39-b2db-c4fb78088fd1" ,
"connection": "database",
"queue": "default",
"payload": "{...}",
"exception": "...",
"failed_at": 1591425946
}
Laravel stores the job's UUID, the queue connection the job uses, the queue the job was
dispatched to, the payload, the exception that was thrown, and the timestamp of when the
job failed.
This makes it easy for us to inspect the exception and decide on what we need to do. For
example, if it's a non-transient error, like a validation error, or the provider is out of
business, we'll need to change the job or the mail service configuration. On the other hand,
if it's a temporary outage, we'll have to retry the job later.
Inspecting the Dead-letter Queue
To inspect the dead-letter queue, Laravel provides us with the queue:failed artisan
command.
php artisan queue:failed
This command outputs a table in the terminal window that contains all the failed jobs

34
records:
+--------------+------------+---------+-------------------------+---------------------+
| ID | Connection | Queue | Class | Failed At |
+--------------+------------+---------+-------------------------+---------------------+
| d615e9c5s... | database | default | SendVerificationMessage | 2022-06-11 15:30:07 |
+--------------+------------+---------+-------------------------+---------------------+
As we can see, the exception details isn't included in the output of this command. That's
mainly for formatting reasons. Displaying the full stacktrace of an exception inside the
terminal will not be readable for a human. It is advisable to use a GUI to inspect the
failed_jobs database table to browse through all the failed jobs and inspect the exception
details.
Retrying a Job Manually
When an email verification job fails, the user won't be able to use the application since their
email isn't verified. They will not get any feedback that there was an internal failure, so they
will keep waiting and waiting until they contact customer support to report the issue.
Typically customer support will forward the incident to developers, who, ideally, know about
the issue by now and are working on fixing it.
Once the issue is fixed, developers will want to send all those failed jobs in the dead-letter
queue. For that reason, Laravel ships with a queue:retry artisan command we can use.
Here for example, we can retry a job by providing the UUID to the command:
php artisan queue:retry 67b31b39-26df-4b39-b2db-c4fb78088fd1
You may think this command processes the job on the spot, but that's not what happens.
This command only re-dispatches the job back to the queue, where it will be treated like any
newly dispatched job. The only difference is that the job will keep its UUID. New jobs always
get a new UUID, but failed jobs keep their UUID.
Here are the steps Laravel takes to retry a job:
Queries the failed_jobs table.1.
Gets the record.2.
Dispatches the job to the queue.3.
Deletes the record.4.

35
Retrying All Failed Jobs
A temporary outage in one of the systems that our application uses can stay for hours, and
sometimes days, depending on how big the issue is. I've seen some services go offline for
hours which caused a huge backlog of failed jobs in the dead-letter queue.
Retrying a large number of failed jobs manually can be a tremendous amount of work. For
that reason, Laravel allows us to retry all of the failed jobs by running a single command:
php artisan queue:retry all
When that command runs, Laravel will go over all failed jobs and dispatch them to the queue
one by one. This, obviously, can take quite some time for a large number of jobs. So if we
run this command and it takes some time to return, don't panic!
Keeping the Dead-letter Queue in Check
As we mentioned, the dead-letter queue can be filled with jobs when things go wrong.
However, for some non-transient failures, a retry won't help. For those cases, Laravel ships
with two commands that can help us keep the dead-letter queue in check.
The first is the queue:prune-failed command:
php artisan queue:prune-failed
When this command runs, it will delete failed jobs older than 24 hours. We can control the
number of hours to retain failed jobs by providing a hours option:
php artisan queue:prune-failed --hours= 48
The other command is the queue:flush command. This one flushes the dead-letter queue
and deletes all failed jobs:
php artisan queue:flush

36
Configuring Automatic Retries
Retrying jobs manually is excellent, but it's not ideal. Most of the work we'd prefer to do
asynchronously involves communicating with remote services, in which transient failures are
expected. These failures include loss of connectivity, unavailability of a service, declines due
to rate limiting, or timeouts. However, they are all self-correcting, and if the same job is
retried after a bit, it is likely to succeed.
Laravel allows us to configure a retry strategy for our jobs. It can be as simple as instructing
workers to retry every job several times before considering it a failure. To do that, we may
start our workers with the --tries option:
php artisan queue:work --tries= 3
This worker will attempt every job 3 times–if it didn't finish–before considering it a failure.
Behind the scenes, Laravel will release it back to the queue so workers can pick it up again.
You can think of releasing a job back to the queue as pushing a new job.
Notice: Every time a worker picks a job up, it increments an internal "attempts"
counter. That's how it keeps track of how many times the job has been retried.
Figure 2-6 shows the three stages of a job that got retried. First, the job was pushed to the
queue by the web app. Then, the worker picks it up. Finally, the job was released back to the
queue when it failed to complete.

37
Figure 2-6. Releasing a job back to the queue.
If the job fails again, it will be released back to the queue again. Finally–if it fails another
time–the worker will move it to the dead-letter queue. Because by this time, the job would
have been tried three times.
You should notice in Figure 2-6 that a released job may not be retried immediately by the
worker if there are other jobs in the queue. Also, if multiple workers are consuming the
queue, different workers may make attempts. There's no guarantee that a job will be retried
immediately nor the same worker will retry it.
Job-Specific Tries
Not all jobs should be retried the same number of times. It's important we put in place a
retry strategy that is tailored for each specific case. If we don't retry a job enough, it will fail
and require manual intervention. If we retry too much, we would be wasting valuable
computing and networking resources.
Laravel allows us to configure the number of tries on a job level by adding a $tries public
property to the class:
namespace App\Jobs;
class SendVerificationMessage implements ShouldQueue
{
public $tries = 3;
}

38
Retrying a SendVerificationMessage job three times seems appropriate. But we may
find it approriate to retry a ProcessPayment ten times.
namespace App\Jobs;
class ProcessPayment implements ShouldQueue
{
public $tries = 10;
}
Whatever value we use in the $tries public property will override the value specified in the
--tries worker option. With that in mind, we can omit the --tries option from the
worker command and let it fail jobs on the first attempt and only configure specific jobs to
retry multiple times.
Delaying Tries
If a job fails to complete and multiple tries were configured, the worker will dispatch the job
back to the queue. That newly dispatched job will be picked up again by a worker. This could
happen in a few minutes, or a few milliseconds, depending on how busy the queue is and
the number of workers consuming jobs from that queue.
The default retry policy that ships with Laravel ensures that a retry would happen as soon as
possible (once a worker is free to pick the job up). In many cases, we may want to control
the interval between retries to give the service enough time to recover from a transient
failure. To do that, we may add a $backoff public property in the job class:
namespace App\Jobs;
class SendVerificationMessage implements ShouldQueue
{
public $tries = 3;
public $backoff = 20;
}
By doing this, we're instructing our workers to retry this job after 20 seconds on each failure.
Figure 2-7 shows a job that got picked up at 00:00 and ran for 10 seconds but didn't
complete, the job was released back to the queue and the worker picked it up again after

39
the 20 second backoff passed at 00:30. This time the job ran for 15 seconds and didn't
complete, so the worker released it back to the queue at 00:45. The worker waited for
another 20 seconds to pick it up again at 01:01.
Figure 2-7. Retrying timeline.
Keep in mind that the backoff time only makes the job invisible to workers during the
interval, it doesn't guarantee the job will be retried exactly after the backoff period passes.
Figure 2-7 shows a unique case in which the worker was not occupied by other work when
the backoff period passed, so it was able to pick the job up immediately. I wanted to stress
this point enough because it's a widespread misconception that causes a lot of confusion
when people think a released job will run directly after the backoff period.
Exponential Backoff
In our example, waiting 20 seconds for the transient failure to resolve seems enough. But
what if the job fails again? That would probably mean the transient failure is a long-term
one. Retrying every 20 seconds can be a waste of resources in that case. It might be a good
idea to increase the delay after each attempt:
namespace App\Jobs;
class SendVerificationMessage implements ShouldQueue
{
public $tries = 3;
public $backoff = [20, 60];
}
Using an exponential backoff, we instructed the worker to delay retrying the job 20 seconds
the first time, but then delay the third retry 60 seconds. Giving the service more time to
recover and saving our system resources for other tasks in the meantime.

40
Notice: If we have $tries = 4, the fourth attempt will be delayed 60 seconds as
well.
We can also configure backoff on a worker level:
php artisan queue:work --backoff=20,60
This will delay attempting all failing jobs processed by this worker for 20 seconds after the
first attempt and 60 seconds for each attempt as it advances.
Not Really FIFO
You may have figured this out already, but dispatching jobs to a queue in a specific order
doesn't mean they will be processed in that exact order. The workers will pick them up in the
exact order, but the processing may not happen in the same order because some jobs may
fail and be returned to the queue and retried later.
Imagine two SendVerificationMessage jobs were dispatched for two users (Figure 2-8),
let's call them Zain and Adam. Adam signed up before Zain so his verification job got
dispatched first. By the time the worker picked Adam's job up, the mail service provider was
down, so it got released back to the queue, and the worker moved to process Zain's job.
Luckily, the service was back online, and the job was processed. Then after the backoff
period passed, Zain's job got picked up by the worker and was processed successfully.
Figure 2-8. Jobs are picked up in exact order but processed in a different order.
There is a way to instruct Laravel to process a chain of jobs in exact order and wait for one
job to finish successfully before moving to the next. We will look into that in a future

41
chapter. But the general rule is that we shouldn't expect jobs to be finished in the same
order they were dispatched. It is another common misconception that I'm hoping to clear up
in this book.
Canceling Abandoned Orders
Now let's move to a different challenge: we'll look into a familiar pattern used in
eCommerce. When users add items to their shopping cart and start the checkout process,
you want to reserve these items for them. However, if a user abandoned an order—they
never canceled or checked out—you will want to release the reserved items back into stock
so other people can order them.
To do this, we will schedule a job once a user starts the checkout process. This job would
check the order status after one hour and cancel it automatically if it wasn't completed by
then.
Delay Processing a Job
Let's see how such a job can be dispatched from the controller action:
class CheckoutController
{
public function store()
{
$order = Order::create([
'status' => Order::PENDING,
// ...
]);
MonitorPendingOrder::dispatch($order)
->delay( 3600);
}
}
The first procedure is creating an order entry and setting the status to pending. After that,
we dispatch the job and chain a delay(3600) method after dispatch(). This method
instructs Laravel to delay processing the MonitorPendingOrder for 3600 seconds (1 hour).
We can also set the delay using a DateTimeInterface implementation:

42
MonitorPendingOrder::dispatch($order)->delay(
now()->addHour()
);
Dispatching jobs with a delay works similarly to releasing jobs that didn't complete back to
the queue with a backoff. During the delay period, the job will be invisible to workers.
Warning: Using the SQS driver, you can only delay a job for 15 minutes.
Here's a quick look inside the handle() method of that job:
public function handle()
{
if ($this->order->status == Order::CONFIRMED ||
$this->order->status == Order::CANCELED) {
return;
}
$this->order->markAsCanceled();
}
When the job runs—after an hour—we'll check if the order was canceled or confirmed and
return from the handle() method. Otherwise, we'll cancel the order if it is still pending.
Sending Users a Reminder Before Canceling
Now let's enhance the user experience and try to increase conversion by sending the user an
SMS notification to remind them about their order before canceling it. To do this, we will
send an SMS message every 15 minutes until the user completes the checkout, or we cancel
the order after 1 hour.
First, let's adjust the delay on the job processing to be 15 minutes instead of an hour:
MonitorPendingOrder::dispatch($order)->delay(
now()->addMinutes( 15)
);
Now when this job runs, we want to check if an hour has passed and cancel the order. Or, if

43
we're still within the hour, we'll send an SMS reminder and release the job back to the
queue with a 15-minute delay.
public function handle()
{
// Check the status of the order
if ($this->order->status == Order::CONFIRMED ||
$this->order->status == Order::CANCELED) {
return;
}
// Cancel the order if it's older than 59 minutes
if ($this->order->olderThan(59, 'minutes')) {
$this->order->markAsCanceled();
return;
}
// Send a reminder SMS message
SMS::send(...);
// Release the job back to the queue with a delay
$this->release(
now()->addMinutes( 15)
);
}
Calling $this->release() inside a job instructs the worker to put the job back in the
queue to be retried later. This method accepts an optional argument representing the delay
we want to set. In our case, we're delaying the processing of the released job for 15 minutes.
Ensuring the Job Has Enough Attempts
Every time the worker picks up the job, it'll increment the attempts counter. And given that
jobs are allowed to be attempted only a single time by default, we need to make sure our
job has enough $tries to run four times:
class MonitorPendingOrder implements ShouldQueue
{
public $tries = 4;
}

44
Assuming the job was dispatched at 00:00 and that the worker was available to pick it up
when the delay period expires, the time for the job will be like this (Figure 2-9):
Figure 2-9. The job is attempted four times.
If the user confirms or cancels the order, say after 20 minutes, the job will be deleted from
the queue when it runs on the attempt at 30 minutes, and no SMS will be sent.
This is because we have this check at the beginning of the handle() method:
if ($this->order->status == Order::CONFIRMED ||
$this->order->status == Order::CANCELED) {
return;
}
Delay and Backoff Periods
As we've mentioned, there's no guarantee that workers will pick the job up after the delay or
backoff period passes. If the queue is busy and not enough workers are running, the
MonitorPendingOrder job in this example may not run enough times to send the three
SMS reminders before canceling the order (Figure 2-10).

45
Figure 2-10. The job runs 3 times only.
To increase the chance of your delayed jobs getting processed on time, you need to ensure
you have enough workers to empty the queue as quickly as possible. This way, when the job
becomes available, a worker will be ready to run it immediately.
Sending Webhooks
Another common use case for queues is handling the task of sending webhooks. Which is a
method to extend applications by allowing 3rd party integrations to subscribe to specific
events. When any of these events occur, the application calls a webhook URL configured by
the integration:
SendWebhook::dispatch($integration, $event);
Inside the SendWebhook job, we're going to use Laravel's built-in HTTP client to send
requests to the integration URL:
use Illuminate\Support\Facades\Http;
class SendWebhook implements ShouldQueue
{
// ...
public function handle()
{
$response = Http::post(
$this->integration->url,
$this->event->data
);

46
}
}
Now every service that offers webhooks must have a retry policy in place. This policy is
usually communicated with developers and partners in the service documentation or
integration agreements.
The challenge we have here is implementing an exponential webhook retry policy in which
the retry backoff is increased by 15 minutes after every attempt. The service keeps
attempting the webhook for 24 hours and sends an alert to the partner at the end of this
period.
Dynamic Exponential Backoff
First, let's work on the 15-minute backoff increase part. We know that to configure an
exponential backoff, we need to define a $backoff property in the job class and provide an
array of backoff periods. But since this job will be be retried times, let's set the backoff
dynamically instead.
To do that, we will handle the webhook failure inside the handle method instead of relying
on the worker's exception handler. Inside our handler, we will release the job back to the
queue with a delay period defined by the number of the current attempt. Check this out:
$response = Http::post(...);
if ($response->failed()) {
$this->release(
now()->addMinutes( 15 * $this->attempts())
);
}
Notice: The attempts() method returns the number of times the job has been
picked up. If it's the first run, attempts() will return 1.
What's happening here is that we release the job back to the queue with an increasing delay.
Here's how the timeline will look like if the job keeps failing:

47
Figure 2-11. Dynamic exponential backoff.
The backoff period between attempts will keep increasing dynamically. We saved a bit of
brain power for other stuff and let the computer build the incremental backoff strategy for
us.
Configuring Job Expiration
Now let's look into retrying the job for 24 hours. We could do the math and calculate how
many attempts a whole day can allow based on our exponential backoff. But there's an
easier way:
class SendWebhook implements ShouldQueue
{
public function retryUntil()
{
return now()->addDay();
}
}
Adding a retryUntil() public method to our job class instructs Laravel to set an expiration
date for the job. In this case, the job will expire after 24 hours.
Warning: The expiration is calculated by calling the retryUntil() method when the
job is first dispatched. If you release the job back, the expiration will not be re-
calculated.
Now the worker will fail this job if it was attempted after the 24-hour period. But since all
jobs by default are allowed only one attempt, we need to configure this job to retry an

48
unlimited number of times. That way, the worker will keep retrying it until it expires. To do
this, we need to set the $tries public property to 0:
class SendWebhook implements ShouldQueue
{
public $tries = 0;
}
Handling Job Failure
Now that we implemented the incremental backoff strategy and configured the job
expiration. It's time we work on the last part of our webhook retry policy, which is sending
an alert to the partner if a webhook keeps failing for over 24 hours.
To do this, we're going to add a failed() public method to our job class:
class SendWebhook implements ShouldQueue
{
// ...
public function failed(Exception $e)
{
Mail::to(
$this->integration->developer_email
)->send(...);
}
}
When a job exceeds the assigned number of attempts or reaches the expiration time, the
worker will call this failed() method and pass a MaxAttemptsExceededException
exception. Inside this method, we will use the Mail facade to send an email to the
integration developer with details of the failing webhook.
We've discussed in a previous challenge that sending mail is an error-prone process as it can
face transient failures. Therefore, we need to have a retry policy in place to handle these
failures. If the mail sending fails inside the failed method of the SendWebhook job, it will
not be retried automatically.
We need to isolate this task into its job so we can configure retries and exponential backoff.
So let's implement a SendWebhookFailedWarning job in our project:

49
class SendWebhookFailedWarning implements ShouldQueue
{
public $tries = 3;
public $backoff = [20, 60];
public function __construct(
private Integration $integration
) {}
public function handle()
{
Mail::to(
$this->integration->developer_email
)->send(...);
}
}
Inside the failed method of the SendWebhook job, we can dispatch this new job:
class SendWebhook implements ShouldQueue
{
public function failed(Exception $e)
{
SendWebhookFailedWarning::dispatch( $this->integration);
}
}
Provisioning a Forge Server
In the previous challenges, we learned we could control the lifetime of a job by limiting
retries or setting a job expiration. At this point, you may think that using expiration is a much
better option for all use cases. However, in this challenge, we'll see that using expiration is a
bit risky and that you might need to think twice before using it.
In this challenge, we'll look into using Laravel Forge to provision a new server. We need to
communicate with the Forge API by sending a POST request to the /servers endpoint. The
request might take a few seconds, and we'll receive the server ID in the response. But that
doesn't mean the server is ready to go.
After Forge responds to our request, it will continue provisioning the server, which may take
a few minutes. So, we'll need to keep checking the server status and update our local

50
storage when the server is ready.
Here's how the store() controller action in which we create the server may look:
public function store()
{
$server = Server::create([
'is_ready' => false,
'forge_server_id' => null
]);
ProvisionServer::dispatch($server, request( 'server_payload'));
}
First, we create a record in our database to store the server and set is_ready = false to
indicate that this server is not usable yet. Then, we dispatch a ProvisionServer job to the
queue.
Here's how the job may look:
class ProvisionServer implements ShouldQueue
{
public function __construct(
private Server $server,
private array $payload
) {}
public function handle()
{
if (! $this->server->forge_server_id) {
$response = Http::timeout( 5)->post(
'.../servers', $this->payload
)->throw()->json();
$this->server->update([
'forge_server_id' => $response['id']
]);
return $this->release(120);
}
$response = Http::timeout( 5)->get(
'.../servers/'.$this->server->forge_server_id,
)->throw()->json();

Random documents with unrelated
content Scribd suggests to you:

F
II.
A new Acquaintance,—The Islands of the Sea,—Making Friends,—
The Natives,—A Festival,—Efforts at Conversation in an unknown
Tongue, —Corbet’s Baby Talk,—Experiments of Bart and Tim,—Pat
comes to Grief.—Overthrow of the French,—Arrival of the Skipper on
the Scene, —He means Business.
INDING that their new acquaintance was so very friendly,
and communicative, and all that, the boys thought that it
would be a good thing to find out from him something about
the various islands which they proposed visiting. Ferguson declared
that he knew as much about the Gulf of St. Lawrence as any man
living, and could tell them all they wanted to know.
“What sort of a place is St. Paul’s Island,” asked Arthur.
The skipper shook his head in silence. “Is St. Pierre worth
visiting?”
“Well—scarcely,” said the other.
“What sort of a place is Anticosti?” asked Bruce.
“Well, you’d best not go within fifty miles of that thar island.”
“What sort of a place is Sable Island?” asked Bart.
“Sable Island!” exclaimed the skipper, staring at them in
astonishment.
“Yes, Sable Island.”
“You mean Cape Sable Island.”
“No; we mean Sable Island.”
The skipper looked at them all with a solemn face.
“Well, boys,” said he, “as to visiting Sable Island, all I’ve got to say
is, I hope you’ll never begin to try it on Sable Island. Why, Sable
Island’s one of the places that seafarin’ men try never to visit, and

pray never to get nearer than a hundred miles to. Sable Island!
Boys,” he continued, after a pause, “don’t ever speak of that again;
don’t even think of it. Give it up at once and forever. I only hope that
you won’t be brought to pay a visit there in spite of yourselves, a
thing which I’m afraid you’re very likely to do if you go cruisin’ about
in an old tub like that much longer. Not but what Sable Island
mightn’t be improved—that is, if the inhabitants only had any
enterprise, and the government that owns it was alive to the wants
of the age.”
“‘Inhabitants!” said Bart; “why, there’s only the keeper and his
family.”
The skipper waved his hand.
“Grant all that,” said he. “Very well. They’re a nucleus, at any rate,
and can give tone and character to the future Sable Islanders. Now,
what your government ought to do with Sable Island is this. They’d
ought to make a good breakwater, first and foremost, so as to have
decent harbor accommodation for passing vessels. Then they’d
ought to connect it with the main land with a submarine cable, so
that the place needn’t be quite so isolated, and have regular lines of
steamers runnin’ backard and forard. Well, then they ought to get up
a judicious emigration scheme, and that thar island would begin to
go ahead in a style that would make you fairly open your eyes. Why,
in ten years, if this plan was carried out, they’d be building a
railroad,—a thing that is needed there more than most anywheres,
the island bein so uncommon long and narrow,—and that bein done,
why, Sable Island would begin to come abreast of the nineteenth
century, instead of hanging back in the middle ages.”
After some further conversation of a similar character, the skipper
proposed to show the boys about the country, and introduce them to
some of the “aristocracy.”
“And there,” said he, “is one of them, now. It’s the priest—and a
precious fine fellow he is, any how, and no mistake. He is priest,
governor general, magistrate, constable, policeman, Sunday school
teacher, town clerk, schoolmaster, newspaper, lawyer, doctor, notary

public, census taker, and fifty other things all rolled into one. He is
the factotum of the Magdalen Islands. They come to him for
everything: to baptize their infants, to marry their young couples,
and to bury their dead. They go to mass on Sundays, and on week
days they go to him for advice and assistance in everything. He visits
the sick, and administers medicine as doctor, or extreme unction as
priest. He settles all their quarrels better than any judge or jury, and
there never ain’t any appeal thought of from his decision. Now, all
this is what I call a species of despotism,—it’s one man power, but it
suits these poor benighted frog-eatin heathen,—and, besides, it’s no
more a despotism than the father of a family exercises. It’s
patriarchal—that’s what it is. It’s wonderful, too, how much honor
the young people hereabouts pay to their fathers, and grandfathers,
and elders genrally. I never knowed anythin like it in all my born
days. Well, now, boys, mind you, all this is goin to be upset. Some
day they’ll be appointin magistrates here, and doctors will come, and
lawyers; then this little community will all be sot by the ears, and—
and they’ll enter upon a career of boundless progress. They’ll get
the ballot-box, and the newspaper, and all the concomitants of
modern civilization; the present patriarchal system’ll be played out,
and the spirit of the age will reign and rule over them.”
By the time the skipper had given utterance to this, they had
approached the priest. He was a mild, venerable man, with a meek
face and a genial smile. He spoke English very well, shook hands
with all, and listened to the skipper’s explanations about their
present visit.
“And now, boys, I’ll leave you for the present,” said the skipper, “to
the care of Father Leblanc, who will do the honors of the island. I’ve
got to go aboard the Fawn to fix up a few things. We’ll meet again in
the course of the day.”
With these words he went down to the beach. The shabbiness of
the costume of the boys had already excited the remarks of the
skipper, but the good Father Leblanc soon saw that in spite of this
they were clever and intelligent.

“We do not often have,” said he, “at this place visitors above the
rank of fishermen, and we have never before had any visitors like
you. I can assure you a welcome, dear boys, from all the good
people here. There is to be a fête to-day in honor of the marriage of
two of my flock. Would you like to go? If so, I invite you most
cordially, and assure you of a welcome.”
This unexpected invitation, thus kindly given, was accepted with
undisguised eagerness; and thereupon the boys accompanied the
priest, who first of all went to his own home, where he offered them
some simple refreshments. The priest’s home was a small cottage of
very unpretending exterior, and very similar to all the other cottages;
but inside there were marks of refined taste and scholarly pursuits. A
few Latin and Greek classics were on a small book-shelf. There was
an harmonium, with some volumes of sacred music, and here and
there were some volumes which were of a theological character. The
entertainment of the priest consisted of some coffee, which the boys
were surprised to find, and which they afterwards unanimously
pronounced to be “perfectly delicious,” and some fresh eggs, with
immaculate bread and butter.
After chatting with the boys for about an hour, the priest
announced that it was time to start, as their destination was on the
opposite side of the island. They accordingly set out at once, and
walked along the slope of a hill. There was no road, but only a
footpath, which served all the purposes of the Magdalen Islanders,
in spite of the skipper’s theories about a railway. On the way the
priest entertained them with stories of his life on these secluded
islands, of the storms of winter, of the ice blockade, of the perils of
the sea, of the vast solitude of the surrounding gulf, where in winter
no ship ever ventures. Yet in spite of the loneliness, he affirmed that
no one here had any sense of desolation, for it seemed to all of the
inhabitants, just as it seems to the inhabitants of other countries,
that this home of theirs was the centre of the universe, and all other
lands strange, and drear, and unattractive.
At length they reached their destination. It was a cottage of rather
larger size than usual, and it seemed as if the whole population of

the island had gathered here. Tables were spread in the open air,
and a barrel of cider was on tap. As they drew near they heard the
sound of a fiddle, and saw figures moving about in a lively dance.
Old men, young men, women, girls, and children were all laughing,
talking, dancing, or playing. It was a scene full of a curious
attractiveness, and exhibited in a striking way the irrepressible
gayety that characterizes the French wherever they go.
At their approach the laughter and the dance ceased for a time,
and the company welcomed the good priest with smiles and kindly
words. The boys also came in for a share of the hospitable welcome,
and as soon as the priest had explained who they were, they were
at once received as most welcome and honored guests.
Unfortunately the boys could not speak a word of French, and the
people could not speak a word of English, so that there was not that
freedom of intercourse between the two parties which might have
been desirable; but the priest did much to bring about this
interchange of feelings by acting as interpreter, and the boys also by
gestures or by smiles endeavored, not without some success, to
make known their feelings for themselves.
The boys soon distributed themselves about at random, and the
good people never ceased to pay delicate little attentions to them by
offering them coffee or cakes, by uttering a few words in the hope
that they might be understood, or, if words were wanting, they took
refuge in smiles. But words were not wanting, and different
members of the party made violent efforts to break through the
restraints which a foreign language imposed, and express their
feelings more directly.
Thus Captain Corbet, who had accompanied the party, finding
himself hospitably entertained by a smiling old Frenchman,
endeavored to make known the joy of his heart.
“Coffee,” said he, tapping his cup and grinning.
“Oui, oui,” said the Frenchman.
“Coffee dood—pooty—nicey—O, velly nicey picey.”

Captain Corbet evidently was falling back upon his “baby talk,”
under the impression that it would be more intelligible to a foreigner.
But this foreigner did not quite understand him. He only shrugged
his shoulders.
“Cooky—cakey—nicey,” continued Captain Corbet, in, an amiable
tone. “All dood—all nicey—velly.”
And he again paused and smiled.
“Plait-il?” said the Frenchman, politely.
“Plate? O, no, no plate for me, an thank you kindly all the same.”
The Frenchman looked at him in a bewildered way, but still smiled.
“Vouley vous du pain?” he asked, at length.
“Pan?” said Captain Corbet; “pan? Course not. What’d I do with a
pan?—but thankin you all the same, course.”
The Frenchman relapsed into silence.
“It was a pooty ’itile tottage,” said Captain Corbet, resuming his
baby talk, “an a pooty tompany, an it was all dood—pooty—nicey.”
But the Frenchman didn’t understand a word, and so at length
Captain Corbet, with a sigh, gave up the attempt.
Meanwhile the others were making similar endeavors. Tom had
got hold of a French boy about his own age.
“Parley vous Français,” said Tom, solemnly.
“Oui,” said the French boy.
“Oui, moosoo,” said Tom.
The French boy smiled.
“Merci, madame,” continued Tom, boldly.
The boy stared.
“Nong—tong—paw,” proceeded Tom, in a business-like manner.
Of this the boy could evidently make nothing.
But here Tom seemed to have reached the limit of his knowledge
of French, and the conversation came to a sudden and lamentable
end.

Bart had carried on for some time an interesting conversation with
smiles and gestures, when he too ventured into audible words.
“Bon!” said he, in an impressive manner; and then touching the
breast of the boy to whom he was speaking, he continued, “You—tu
—you know—you’re bon;” then, laying his hand on his heart, he
said, “me bon;” then, pointing to the cup, “coffee bon;” then
sweeping his hand around, he added, “and all bon—house bon,
company bon, people bon.”
“Ah, oui,” cried the boy. “Oui, je vous comprends. Aha, oui, la
bonne compagnie, le bon peuple—”
“Bon company, bon people, bon company, bon people,” cried Bart,
delighted at his success in getting up a conversation; “bon coffee,
too; I tell you what, it’s the bonnest coffee that I’ve tasted for many
a long day.”
At this the boy looked blank.
“Parley vous Français?” asked Bart, in an anxious tone.
“Oui,” said the boy.
“Well, then, I don’t,” said Bart; “but the moment I get home I
intend to study it.”
And at this stage Bart’s conversation broke down.
Pat chose another mode of accomplishing the same end. Captain
Corbet had been acting on the theory that foreigners were like
babies, and could understand baby talk. Pat, in addition to this,
acted on the theory that they were deaf, and had to be addressed
accordingly. So, as he was refreshing himself with coffee and cakes,
he drew a little nearer to the old woman who had poured it out for
him, and bent down his head. The old woman was at that moment
intent upon her coffeepot, and did not notice Pat. Suddenly Pat, with
his mouth close to her ear, shouted out with a perfect yell,—
“Bully for you! and thank you kindly, marm!”
With a shriek of terror the startled old woman sprang up and fell
backward. The chair on which she had been sitting, a rather rickety
affair, gave way and went down. The old lady fell with the chair

upon the ground, and lay for a moment motionless. Pat, horror-
struck, stood confounded, and stared in silence at the ruin he had
wrought. The bystanders, alarmed at the shout and shriek, crowded
around, and for a moment there was universal confusion. Among the
bystanders was the priest. To him Pat turned in his despair, and tried
to explain. The priest listened, and then went to see about the old
woman. Fortunately she had fallen on the soft turf, and was not at
all hurt. She was soon on her feet, and another chair was procured,
in which she seated herself. The priest then explained the whole
affair. Pat was fully forgiven, and the harmony of the festival was
perfectly restored. But Pat’s laudable efforts at maintaining a
conversation had received so severe a check that he did not open his
mouth for the rest of the day.
The festival went on. Fun and hilarity prevailed all around. The
dancing grew more and more vigorous. At length the contagion
spread to the elder ones of the party, and the boys were astonished
to see old men stepping forth to skip and dance about the green;
then old women came forward to take a part, until, at length, all
were dancing. The boys stood as spectators, until at length Bart
determined to throw himself into the spirit of the scene. He
therefore found a partner, and plunged into the dance. The others
followed. Captain Corbet alone remained, seated near a table,
viewing the scene with his usual benevolent glance.
In the midst of this festive scene the skipper approached. He
walked with rapid steps, and, without hesitating an instant, seized a
partner and flung himself, with all the energy of his race, into the
mazy dance.
“I don’t often dance, boys,” he remarked, afterwards, “but when I
do, I mean business.”
It was evident that on this occasion the skipper did mean
business. He danced more vigorously than any. He jumped higher;
he whirled his partner round faster; he danced with more partners
than any other, for he went through the whole assemblage, and led
out every female there, from the oldest woman down to the smallest
girl.

Most of the time he chatted volubly, and flung out remarks which
excited roars of laughter. He won all hearts. He was, in fact, an
immense success. The boys wondered, for they had not imagined
that he could speak French.
He alluded to this afterwards.
“We have a natral affinity with the French down in New England,”
said he. “When America was first colonized, our forefathers had to
fight the French all the time. The two races were thus brought into
connection. Our forefathers thus caught from the French that nasal
twang with which the uneducated still speak English. You find that
twang among the uneducated classes all over the British provinces
and New England. It’s French—that’s what it is. Corbet and I are
both uneducated men, and we both speak English with the French
twang. I speak French first rate; and Corbet there could speak it first
rate also, if he only knew the language perfectly.”
These remarks the boys did not quite know how to take. The
skipper seemed to have a bantering way with him, and spoke so
oddly that it was impossible for them to make out half of the time
whether he was in earnest or only in jest.

A
III.
Friendly Advice and dismal Forebodings.—Once more upon the
Waters, yet once more.—Due North.—A Calm.—The Calm continues.
—A terrible Disclosure.—Despair of Corbet.—Solomon finds his
Occupation gone.—Taking Stock.—Short Allowance.
NOTHER day was passed very pleasantly at the Magdalen
Islands, and then the boys concluded that they had seen
about all that there was to be seen in this place. As the
question where next to go arose, they Concluded to ask the skipper.
“Well, boys,” said he, “in the first place, let me ask you if you’ve
ever heard of Anticosti?”
“Of course we have,” said Bart.
“Well, don’t go there; don’t go near it; don’t go within fifty mile of
it; don’t speak of it; don’t think of it; and don’t dream of it. It’s a
place of horror, a howling wilderness, the abomination of desolation,
a haunted island, a graveyard of unfortunate sailors. Its shores are
lined with their bones. Don’t you go and add your young bones to
the lot. You can do far better with them.”
“Well, where do you advise us to go?” asked Arthur.
The skipper thought for a few moments without answering.
“Well,” said he, “you know Sable Island.”
“Yes,” said Bart, in some surprise.
“Well,” said the skipper, impressively, “don’t go there; don’t go
within a hundred miles of it; don’t speak of it; don’t think of it; don’t
dream of it.”
“But you’ve said all that to us before,” said Bruce. “We want to
know where we are to go, not where we are not to go.”

“Well,” said the skipper, “I am aware that I’ve said all this before,
and I say it a second time, deliberately, for the simple purpose of
impressing it upon your minds. There’s nothin like repetition to
impress a thing on the memory; and so, if you ever come to grief on
Anticosti, or on Sable Island, you’ll remember my warnin, and you’ll
never feel like blamin me.”
“But where ought we to go?” asked Bruce.
“Well, that’s the next point. Now, I’ve been thinkin’ all about it,
and to my mind there ain’t any place in all this here region that
comes up to the Bay of Islands, Newfoundland.”
“The Bay of Islands?”
“Yes, the Bay of Islands, on the west coast of Newfoundland. It’s a
great place. I’ve been there over and over, and I know it like a book.
Thousands of vessels go there every season. It’s one of the best
harbors in the gulf. It’s one of the most beautiful places in the world.
The air is bracing, the climate salubrious, the scenery inviting; and it
only needs a first-class hotel with all the modern improvements in
order to become a number one waterin-place. Yes, by ginger!” he
continued, “you plant a first-class hotel there, and let that there
place become known, and there’s nothin to prevent it from goin
ahead of Long Branch or Newport, or any other place you can
mention.
“Then,” continued the skipper, “if you wanted to go any further,
you might go up the Straits of Belle Isle, and round Newfoundland.
If you had time, you might take a run over to Greenland; it’s gettin
to be quite a place, a fashionable resort in the hot summer; but
perhaps you won’t have time, and won’t care about doin more than
cruisin round Newfoundland, and then home.”
Once more the skipper’s tone seemed somewhat extravagant to
the boys, and they did not know how to take it.
“O, well,” said Bart, “we don’t want to go to Greenland this
season. When we do go there, we shall probably go for good; but
just now, we want to confine ourselves to the gulf. If you can really

recommend the Bay of Islands, perhaps we had better go there; that
is,” added Bart, “unless you think we had better go to Iceland.”
The skipper looked at Bart for a few moments in silence, and a
smile gradually passed over his face.
“Well,” said he, after a pause, “that’s the identical place that I was
just going to recommend, when you took the words out of my
mouth. The fact is, boys, with that old tub of yours you might as
well go to Iceland as anywhere else. Every time I look at it I am
thunderstruck. What were your fathers and mothers thinkin of when
they let you come away up here in such an old rattle-trap?—an old
tub that isn’t worth being condemned! Do you think you’ll ever get
home again in her? Not you. Do you know where that old tub’s
bound to go before the end of this season? Down to the bottom of
the sea; and if you don’t go in her, you may bless your lucky stars. I
only wish I wasn’t otherwise engaged. I’d make you all clear out at
once, and come aboard the Fawn.”
Captain Corbet was not present, and did not hear these insulting
reflections upon his beloved Antelope, and therefore was spared the
pain which they would have caused to his aged bosom; but the boys
were not the ones to listen to such insinuations in silence. The
Antelope was dear to them from past associations, and they all
began at once to vindicate her character. They talked long and
eloquently about her. They spoke of her speed, soundness, and
beauty. They told of her performances thus far.
At all of which the skipper only grinned.
“Mark my words, boys,” said he; “that there tub is goin to the
bottom.”
“Well, if she does, she’ll get up again,” said Bart.
The opinions of the two parties were so different that any further
debate was useless. The skipper believed that they were bound for
the bottom of the sea; the boys on the contrary had faith in the
Antelope. The end of it all was, that they concluded to take the
skipper’s advice in part, and sail for the Bay of Islands. This place
was one which they all were desirous of visiting, and they thought

that when they had gone that far, they could then decide best where
next to go.
They were to leave the next morning. That evening they took
leave of the friendly skipper.
“Boys,” said he, “I’m afraid we’ll never meet again; but if you do
get back safe from this perilous adventure of yours, and if any of
you ever happen to be at Gloucester, Massachusetts, I do wish you’d
look me up, and let me know. I’d give anything to see any one of
you again.”
With these words the skipper shook hands with each one of them
heartily, and so took his leave.
Early on the following morning the Antelope spread her sails and
began once more to traverse the seas, heading towards the north.
The wind was fair, and all that day they moved farther and farther
away from the Magdalen Islands, until at length towards evening
they were lost to view in distance and darkness.
On the next day they were all up early. They saw all around a
boundless expanse of water. No land was anywhere visible, and not
a sail was in sight. This was a novelty to the boys, for never yet had
any of them had this experience in the Antelope. Some of them had
been out of sight of land, it is true; but then they were in large
ships, or ocean steamers. Being in such a situation in a craft like the
Antelope, was a far different thing. Yet none of them felt anything
like anxiety, nor had the slurs of the skipper produced any effect
upon their affectionate trust in their gallant bark, and in their
beloved Captain Corbet.
Certainly on the present occasion there was little enough cause for
anxiety about the sea-worthiness of the Antelope. The sea was as
smooth as a mirror, and its glassy surface extended far and wide
around them. There was not a breath of air stirring. They learned
from Wade that the wind had gradually died away between sundown
and midnight, until it had ceased altogether. They were now in a
dead calm.

None of the party was very well pleased at this. They all wished to
be moving. They disliked calms, and would have much preferred a
moderate gale of wind. The Antelope, however, was here, and there
was no help for it. She was far away from land. She lay gently rising
and falling, as the long ocean rollers raised her up and let her down;
and her sails flapped idly in the still air, at the motion of the vessel.
The boys did the best they could under the circumstances, and tried
to pass away the time in various ways. Some of them tried to sleep;
others extemporized a checker-board, and played till they were
tired; others walked up and down, or lounged about. All of them,
however, found their chief employment in one occupation, and that
was eating. Ever since they had been on the water their appetites
had been sharpened; and now that they had nothing else to do, the
occupation of eating became more important and engrossing. To
prolong the repast while it was before them as far as possible, and
then to anticipate the next, were important aids towards killing the
time.
All that day the calm continued: on going to bed that night, the
boys confidently looked forward to a change of weather on the
following day. The night was calm. The following day came. They
were all up betimes. To their deep disappointment they found no
change whatever. There was the same calm, the same unruffled sea,
the same cloudless sky. Not a sail was visible anywhere, and of
course there was no sign of land on any quarter.
The second day the time hung more heavily on their hands. Some
of them proposed fishing; but they had no hooks, and moreover no
bait. Pat proposed fashioning a spike into a hook; fastening it on a
line, and fishing for sharks, and worked all day at a rusty spike for
this purpose. Unfortunately, he could not get it sharp enough, and
so he had at length to give it up.
Captain Corbet was perhaps the most impatient of all; and this
seemed singular to the boys, who thus far had known him only as
the most patient and the most enduring of men.
On this occasion, however, his patience seemed to have departed.
He fidgeted about incessantly. He kept watching the sea, the sky,

and the horizon, and occupied himself for hours in all the various
ways common among seamen, who indulge in the superstitious
practice of trying to “raise the wind.” One mode consisted in
standing in one position motionless for half an hour or more,
watching the horizon, and whistling: another was a peculiar
snapping of the fingers; another was the | burning of some hairs
pulled from his own venerable head. These and other similar acts
excited intense interest among the boys, and helped to make the
time pass less slowly. Unfortunately, not one of these laudable
efforts was successful, and the obstinate wind refused to be “raised.”
That day the boys detected something in their meals which
seemed like a decline of skill on the part of Solomon. There was a
falling off both in the quantity and in the quality of the eatables.
Only four potatoes graced the festive board, and a piece of corned
beef that was quite inadequate to their wants. The tea was weak,
and there was very little sugar. There was only a small supply of
butter, and this butter seemed rather unpleasantly dirty.
On the following day all this was explained. Hurrying up on deck
at early dawn, they saw the scene unchanged. Above was the
cloudless sky, all around the glassy sea, and before them stood
Captain Corbet, the picture of despair. By his side stood Solomon,
with his hands clasped together, and his head hanging down.
“It’s all my fault, boys,” said Captain Corbet, with something like a
groan. “I was to blame: But I declare, I clean forgot. And yet what
business had I to forget? my fustest and highest duty bein to
remember. And here we air!”
“Why, what’s the matter?” asked Tom, who, like all the rest was
struck by Captain Corbet’s despairing attitude and words.
“I won’t hide it any longer, boys,” said he; “it’s this calm. I didn’t
calculate on bein becalmed. I thought only of head winds, and then
we could hev put back easy; but a calm! Why, what can you do?”
“Hide it?” Cried Bruce. “Hide what? What do you mean by this?
What would you want to put back for?”
Captain Corbet groaned.

“For—for pro—provisions, dear boys,” he said mournfully, and with
an effort.
“Provisions!” repeated Bruce, and looked very blank indeed. All the
boys exchanged glanced, which were full of unutterable things.
There was silence for some time.
Tom was the first to break it.
“Well, what have we?” he, asked, in his usual cheery voice. “Come
captain, tell us what there is in the larder.”
“Ask Solomon,” said Captain Corbet, mournfully.
“Well, Solomon, tell us the worst,” said Tom.
But Solomon would not or could not speak. He raised his head,
looked wildly around, and then hurried away.
Captain Corbet looked after him, and heaved a heavy sigh.
“Wal, boys,” said he, “the fact is, Solomon and me, we’ve been
talkin it all over. You see, he considers himself cook, and cook only,
and looks to me for the material. It’s all my fault. I forgot. I thought
there was lots till yesterday mornin. Then Solomon told me how it
was. I’d ort to have laid in a supply before leavin Bay de Chaleur;
but as I said, I forgot. And as for Solomon, why, he’s been calmly a
continooin of his cookery, same as if he was chief cook of a fust-
class hotel, and all the time he was in a becalmed schewner. He told
me all about it yesterday mornin; but I says, ‘Don’t tell the boys;
mebbe the wind’ll change, and I’ll sail for the nighest port.’ So he
didn’t, except so far as you might have guessed, from the meals
which he served up; pooty slim they were too; but he did his best.”
“Well,” said Tom, with unaltered self-possession, “it would have
been better for us to have known this yesterday morning; but that
can’t be helped. So we have no more provisions?”
“Precious little,” said Captain Corbet, mournfully.
“Have we any?” asked Tom.
“Wal,” said Captain Corbet, “the tea’s all gone; and the coffee, and
all the potted meats, and the apples, and the taters, and the turnips

and carrots, and all the vegetables, and the smoked provisions, and
you had the last mite of corned beef yesterday.”
“But what is there left?” asked Tom.
“Only two or three papers of corn starch,” said Captain Corbet,
with an effort, “and, I believe, a half box of raisins, and a little rice.”
“And nothing else?”
“Not a hooter,” said Captain Corbet, despairingly.
Tom was silent. The boys all looked at one another with anxious
faces, and then began to talk over the situation.
The result was, that first of all they made Solomon produce
everything in the shape of eatables that remained on board.
Solomon ransacked the vessel, and laid everything out on the cabin
table.
It was not a very large supply, and the display created additional
uneasiness in the minds of the boys.
There were,—
3 papers of corn starch, 1 lb. each.
1 ham bone.
l box raisins.
1 lb. rice.
6 biscuits.
1 bowl soup.
4 carrots.
1 potato, turnip.
2 apples.
1 oz. tea.
This was all—absolutely all on board the Antelope for the
sustenance of no less than nine human beings, all of whom were
blessed with excellent appetites. Fortunately, there was a sufficient
supply of fresh water, so that there was no trouble on that score.

But this supply of food, even when husbanded with the greatest
care, could scarcely last more than one day,—and here they were in
the middle of the Gulf of St. Lawrence, and becalmed!
The circumstances in which they were, excited the deepest
anxiety in the minds of all. A grave and earnest discussion followed
as to the best course to be pursued. First of all, they all resolved to
deny themselves as far as possible, and make their supply of
provisions last three days. This could be done by making a very thin
soup out of the ham bone with the potato and turnip. The raisins
were to be cooked with the corn starch and rice, in one general
mess, which was to be carefully divided day by day. The biscuits,
carrots, and apples were to be reserved.
After this they decided to try and construct something like oars,
and propel the Antelope in that manner.
The provisions were divided and cooked in accordance with this
decision. They all went without breakfast, for they had decided to
eat but one meal per day. At midday they partook of this important
meal, which consumed one third of their whole stock. But little was
afforded out of that one meal for each individual, and each one felt
able to consume the whole repast, instead of the beggarly ninth part
which fell to him. Poor Captain Corbet refused at first to eat, and so
did Solomon, for each reproached himself as the cause of the
present famine; but the boys put a stop to this by refusing also to
eat, and thus compelled Solomon and the captain to take the
allotted nourishment.
As to the oars or sweeps, the plan proved a total failure. There
was nothing on board which could be used for that purpose. There
was but one small oar for the boat, and they could find nothing else
that could serve for an oar except the spars of the schooner, and
they were not quite prepared to resort to these. Even if they had
done so, there was not an axe or a hatchet on board with which to
fashion them into the requisite shape. There was, in fact, no tool
larger than a pocket knife, except perhaps the table knives, and they
were too dull.

The calm continued.
Thus the first day of their famine passed.
They went to bed hungry.
They awaked famished, and found the calm still continuing. There
was no breakfast for them. The long hours passed slowly. In vain
Captain Corbet whistled for a wind. The wind came not.
Dinner was served at midday. Each one ate his meagre share.
Each one felt that this repast only tantalized his appetite, rather than
satisfied it. Solomon was in despair. Captain Corbet heaped upon
himself never-ending reproaches. Wade sat stolid and starving on
the deck. The boys stared, with hungry eyes, around the horizon.
There was not a sign of land; there was not a sail to be seen.
So the second day passed away.

T
IV.
The third Day.—A strange Sail.—Below the Horizon.—Making
Signals.—No Answer.—Weary Waiting.—Starvation stares them in the
Face.—A long Day.—Hope dying out.—A long Discussion upon the
Situation.—The last Meal.—Bruce and Bart come to a desperate
Determination.—The secret Resolve.
HE third day came.
The boys slept soundly during the night, and were up
early. As they took their first look all around, their feelings
were those of deep despondency; for far and wide, as before, there
was nothing visible but the smooth sea and the cloudless sky. The
calm continued, and all the east was glowing with the fiery rays of
the rising sun.
Suddenly there was a cry from Phil.
“A ship! A ship!”
“Where? Where?” asked all the others.
“There! There!” cried Phil, in intense excitement, pointing towards
the east, where the fiery sky rose over the glowing water. Looking in
the direction where he pointed, they all saw it plainly. It was indeed
as he said. It was a ship, and it was now plainly visible, though at
first, on account of the glare, none of them had noticed it but Phil.
As they stood and looked at it, every one of them was filled with
such deep emotions of joy and gratitude that not a word was said.
Captain Corbet was the first to break the solemn silence.
“Wal, I declar,” said he, “it’s ben so dim all along that I didn’t
notice her; and then it kine o’ got so bright that the glare dazzled my
eyes; but there she is, sure enough; and now all we’ve got to do is
to manage to get into communication with her.”

The boys made no answer, but stood looking in silence. Every
minute the glare lessened; then the sun rose, and as it ascended
above the horizon, the form of the strange ship became fully
revealed.
It was a ship apparently of considerable size; but her hull was low
down in the water, and only her masts were visible. She seemed to
lie below the horizon, yet was as plain to the eye as though she had
been only five miles away.
“Well, boys,” said Bruce, at length, “I don’t know how you feel, but
for my part I feel like taking the boat and going off to her at once.
I’m sick of this fare, and should like to get a good breakfast. What
do you think, captain?”
Captain Corbet shook his head.
“Wal,” said he, “I don’t exactly seem to see my way clear to
approvin of you takin a row for such a matter as twenty mile or so.
We’d never see you again.”
“Twenty miles!” exclaimed Bruce. “Why, it doesn’t look like more
than two.”
The captain smiled.
“Why, you can’t see more of her than her masts,” replied Captain
Corbet; “and a ship that’s down below the horizon far enough to
hide her hull is a pooty good distance off—twenty mile, at least.”
At this Bruce was silent. Captain Corbet’s remarks were
unanswerable, and he did not yet feel prepared to row so great a
distance as twenty miles.
At length Bart went to the cabin, and returned with a spy-glass.
This instrument did not belong to Captain Corbet, for the venerable
navigator was strongly prejudiced against any such instruments, and
the dimmer his eyes grew, the stronger grew those prejudices. It
belonged, in fact, to Bruce, who had provided himself with it before
leaving home. Armed with this, Bart took a long look at the stranger.
Then he passed the glass to Bruce, and then all the boys, in turn,
took a look.

The strange ship already appeared surprisingly distinct for a vessel
that lay below the horizon; and on looking at her through the glass,
this distinctness became more startling. Most of her sails were
furled, or rather, there appeared to be no sails at all, except the jib.
The fore and main-top gallant masts were gone. She appeared,
indeed, to have encountered a storm, in which she had lost her
spars, and the present calm seemed very little in accordance with
her appearance.
The comments which the boys made upon the appearance of the
stranger excited Captain Corbet’s curiosity to such a degree that he
surmounted his prejudices, and condescended to look through the
glass. His astonishment at the result was due rather to his own
ignorance of glasses than to anything in the strange ship; but after
he had become somewhat more familiar with the instrument, he
began to pay attention to the object of his scrutiny.
“The fact is,” said he, after a long and careful search, “it doos railly
look jest for all the world as if that thar craft has been in a storm,
and lost her spars and sails. Perhaps he’s in distress. Perhaps they’re
watching us more anxiously than we’re watching them.”
“I wonder if they can see us?” said Bruce.
“I’m afraid not,” said Bart, “we’re so small.”
“But they’ve got a glass.”
“Yes, and they’d be sweeping the horizon for help.”
“I wish we could get nearer.”
“If they’re hard up, they might row to us.”
“Is it any use to signalize, captain?” asked Tom.
“Not a mite,” said Captain Corbet. “You can’t signalize to a vessel
so far away; at least I never heard of such a thing.”
“O, well, captain,” objected Bruce, “you see they have glasses. We
could see any signals if they were to hoist them, and they can see
us as well as we can see them, of course.”
“Wal,” said Captain Corbet, thoughtfully, “perhaps they can; and if
so, I’m sure I don’t see why we mayn’t try. So you may as well hist

that thar flag o’ yourn, boys. It can’t do any harm, at any rate.”
This proposal was at once acted upon. Several of the boys sprang
aft, and seizing the lines, began to lower and elevate, incessantly,
the proud, yet somewhat battered banner of the B. O. W. C.—the
banner whose pictured face had so often grinned at them through
many an adventure, in storm and in calm. It gave them an
occupation; it also served to excite hope; and so, for several hours,
the flag never ceased to rise and fall,—the boys taking turns at it,
and one relieving the other, so as to keep a fresh hand always at the
work. This continued till midday; but at length they gave it up in
disgust.
They gave it up because it had not produced the slightest result,
nor excited the smallest attention; nor had the circumstances of
their situation changed in any respect whatever. Far away lay the
ship, and no more of her was visible. Nothing but her masts
appeared to their eyes; not a particle of her hull could be seen. She
seemed somewhat longer now, and some of them accounted for this
on the ground that she had changed her position somewhat, and
presented her broadside more than she had done in the morning.
The weather had not changed, nor were there any signs whatever
of a change. The sky was still as cloudless as ever, and not the
faintest fleck disturbed the expanse of blue that hung above them.
The sea was unruffled, nor was there any puff of wind to agitate its
surface.
Early in the morning, when that strange ship first appeared, they
had hoped that a wind might arise before long to bring them
together; or, if a wind did not come, that at least the currents of the
sea might drift them into closer proximity; but now there began to
arise a dark fear that, instead of drifting nearer together, they might
be carried farther asunder, and that this strange ship, which had
thus been borne so mysteriously to their sight during the darkness,
might, on the advent of another day, be borne as mysteriously out of
their sight. With anxious eyes they watched her form, testing it in
every possible way, to discover whether the intervening space had
increased or lessened. Some of the more desponding ones were

convinced that they were drifting asunder; others, more hopeful,
maintained that they were nearer; while others, again, asserted that
their respective positions had not changed. And, in fact, it was
evident from the very dispute itself, that the position of the two
vessels had not very greatly altered.
Half of the day had passed. Another half remained; and after that,
what? Night and darkness, and then how easily could they drift away
from this stranger, on which they had been placing such hopes! How
could they expect that the rest of the day would be any different
from the beginning?
Midday had come, and this was the time for their single daily
meal. Moreover, this meal was the last,—the last of the three
portions which they had set aside for the consumption of three days.
Here arose a solemn question.
Should they eat up all of this last portion? or should they divide it
into two parts, reserving something for the possible emergency of
the next day? The moment that this was proposed, they all decided
at once to reserve something, and not to devour at once all that was
left. They determined to deny themselves for this day for the
security of the morrow; and, hungry though they were, they
preferred to have a meagre repast with hope, rather than a fuller
repast with despair. And so their dinner was divided, and one portion
set aside for the next day. Meagre indeed and inadequate was this
repast for these long-fasting and ravenous boys; but there was no
help for it; and as yet they had not quite reached the worst. They,
therefore, all tried most strenuously to look on the bright side, make
the best of their situation, and cheer one another with remarks of a
hopeful and encouraging character.
Dinner was prolonged as far as possible. Then came the long
hours of the afternoon. Gradually the efforts of the boys to keep up
their own spirits and encourage one another grew feebler and
feebler. From time to time they made faint efforts to find occupation
for themselves, by resorting to the flag, and actively lowering and
hoisting it. But the greater part of the time was spent in silently and

sadly staring at the strange ship, sometimes through the glass,
whenever they could get the chance, but generally without it. The
remarks grew more and more infrequent. The hoplessness of their
situation began to weigh down more and more the spirits of each,
and at length they, one and all, relapsed into silence. Solomon kept
out of sight. Wade sat, as usual, stolid and passive. Captain Corbet
stood at the helm, looking in all directions, at sea and sky, with an
unchanged expression of heart-broken melancholy. So the time
passed.
The afternoon was far worse than the morning: in every respect.
The moral tone of the whole party had declined, and the whole
scene around presented no encouraging feature. In the morning
they had been inspired by the hope of making communications with
the ship, but now this hope died out more and more with every
passing moment.
At length the sun went down, and then the shadows of the
gloomy night followed slowly and steadily. One by one the shades
passed over the distant ship, until at last they stood staring at the
place where they had seen her, but where now they could see
nothing but darkness. This completed their despondency, and the
gloom around was commensurate with that which now fell darkly
and desparingly over the soul of each.
For a long time they wandered up and down the deck. No one
spoke. Each one was involved in his own gloomy thoughts. At
length, one by one, they retired to their beds, with the hope of
forgetting their cares in sleep.
Bruce and Bart were left on the deck alone. All the rest had gone
below. Around all was dark. Both the boys were pacing up and down
restlessly on opposite sides of the deck.
At length Bruce stopped. “Bart,” said he, in a low voice, “is that
you?”
“Yes,” said Bart. “Look here. I’ve got something I want to tell you.”
At this Bart came up to him in silence.
“I don’t like this style of thing,” said Bruce.

Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.
More than just a book-buying platform, we strive to be a bridge
connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.
Join us on a journey of knowledge exploration, passion nurturing, and
personal growth every day!
ebookbell.com