I've got a problem while using Laravel's job Deserialization.
This is the Job class that is queued in the database:
class SendRatingEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order, $user;
public function __construct(User $user, Order $order)
{
$this->order = $order;
$this->user = $user;
}
public function handle()
{
if ($this->order->isRatedByUser($this->user->id)) {
return;
}
Mail::to($this->user->email)->queue(new RatingEmail($this->order, $this->user));
}
}
In the class Order.php, I dispatch this job like this:
class Order {
function queueRating()
{
$when = Carbon::now()->addDays(env('ORDER_DAYS_RATING', 8));
dispatch((new SendRatingEmail($this->buyer, $this))->delay($when));
}
}
So the problem is in the job's handle() function, specifically the error is:
Call to undefined method Illuminate\Database\Query\Builder::isRatedByUser()
It seems as though Laravel gives me the wrong object, instead of App\Order it gives me the QueryBuilder. In the queueRating() function I have checked that the types given in the constructor are the expected types. I have even tested a workaround which also didn't seem to work:
if($this->order instanceof \Illuminate\Database\Query\Builder) {
$this->order = $this->order->first();
}
Also I have looked in the jobs table, and it seems as if the saved models are correct (App\Order)
Edit
Here is the code where the queueRating() function is called. The file is StripeController which handles credit card payments.
public function orderPaid($order) {
$order->payment_done = 1;
$order->save();
$order->chat->open = 1;
$order->chat->save();
$order->queueRating();
}
I found the problem, as always the problem is not where I looked. As it turns out, the code is completely fine, but I forgot to restart the queue worker on the staging server, which meant that changes in the code base were not applied in the queue worker. So the application and queue used different versions.
It is even stated in the Laravel Queue Documentation:
Remember, queue workers are long-lived processes and store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers.
I use the bugtracker Bugsnag, which shows the error and also the line numbers in the code. I had noticed that the line number where the error occurred mismatched the real line number, but couldn't figure out that this was causing the problem.
Related
I'm trying to dispatch jobs from a controller, but sometimes the queue works, and sometimes it will execute the code immediately. On the other hand, dispatching from a command works fine, so I wonder what the difference could be.
Things that I have tried:
I tried database and Redis connections and got the problem for both connections
Without running queue listen/work, the job will randomly execute, so listen/work cannot be the problem
Checked webserver/PHP log for errors, no result
Something goes wrong before the static dispatch function. When I put a die in the static dispatch function, it will sometimes kill the app, and sometimes it will still run decode in the handle immediately....
vendor/laravel/framework/src/Illuminate/Foundation/Bus/Dispatchable.php
public static function dispatch()
{
die('xxx');
return new PendingDispatch(new static(...func_get_args()));
}
I'm trying to find what happened before the dispatch function, but I cannot find any function that executes before the static handle function. I have the feeling that something is crashing in PHP and alternatively execute the job immediately, but I cannot find where that happens in the core.
Controller
class CreateTestJob
{
public function __invoke(Request $request)
{
TestJob::dispatch(rand(0, 999999));
}
}
Job
class TestJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct($test)
{
$this->test = $test;
}
public function handle()
{
sleep(30);
echo 'done';
}
}
I also tried to create a syntax error in de handle function of the TestJob so that I can see the stack trace, but it shows "1 unknown frame". I wonder what that is
you can check fail_jobs table if there is any error while running your job.
The job will be immediately executed only when you use synchronous driver or synchronous dispatch(for use in local development).
<?php Somejob::dispatchSync();
Jobs on Redis and database connection will be executed only when you run queue worker.
php artisan queue:work
I'm using the Laravel queue through redis to send notifications. But whenever I pass models to the notifications, their properties are outdated when the notification is sent through the queue.
This is basically what I'm doing:
In controller (or somehwere else):
$thing = new Thing(); // Thing is a model in this case.
$thing->name = 'Whatever';
$thing->save();
request()->user()->notify(new SomethingHappened($thing))
SomethingHappened.php:
<?php
namespace App\Notifications;
use App\Thing;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class SomethingHappened extends Notification implements ShouldQueue
{
use Queueable;
public $thing;
public function __construct(Thing $thing)
{
$this->thing = $thing;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
dump($this->thing->name); // null
return (new MailMessage())
// ...
// ...
// ...
;
}
}
Now when I add a delay of a few seconds (i.e. (new SomethingHappened($thing))->delay(now()->addSeconds(5))), the retrieved model is up-to-date. Also, before I deployed enough queue workers (and they were lagging behind on a filling queue), this issue didn't exist. Therefore, it appears now that when queue job gets processed really quickly, it doesn't retrieve the model from the database correctly or the model isn't saved yet. I have no idea why this could be happening, since the save call on the model is clearly executed (synchronously) before dispatching the notification, so there should be no way it isn't saved yet when the job is processed.
I'm running on Kubernetes and using a MySQL database. Everything is on GCP (if this is relevant).
Does anyone have an idea? Adding a delay of a few seconds is not a great solution and shouldn't be necessary.
It appears our database was using too much CPU, causing it to switch to the failover regularly. I think this was the cause of the issue. After implementing tweaks reducing the database load, the problem was resolved. A workaround for this issue would be to add a slight delay (as noted in my question).
I'm not sure why this only seemed to affect notifications, and not other queue jobs or requests. If anyone has a more detailed explanation, feel free to post a new answer.
Introduction / System Architecture
Server A - Laravel Installation, Database A
Server B - RabbitMQ Server
Server C - Laravel Installation, Database B
Server A is an API Endpoint, only receiving calls from remote sources. Depending on the call, it'll add a job to the RabbitMQ Server (Server B), which in turn is processed/listened to by Server C.
Server A contains a local copy of the exact same job that is processed by Server C. The job handler code and constructor are shown below (Job Code).
The Issue:
Server A can not serialize the job and add it to the queue, as it attempts to access information about a monitor that only exists in Database B (Server C). Server A has a copy of the model, but does not contain the actual database tables or records as it has no use for them - it's only meant to serialize the job and say "This is what you (Server C) should be doing."
However, upon issuing the job, it's also attempting to fetch database information (likely to serialize the exact data that will be required), which it fails to do as the records don't exist there.
My understanding of Laravel's SerializesModels was specifically that it would only serialize the model call itself, without actually doing anything database related. This does not appear to function, or I am misunderstanding/using it incorrectly - although very little documentation appears to be available.
Workarounds: One possible workaround would be to simply give Server A access to the database on Server C. This is in this case not an option, as it would break the design which is intended for high availability (where the API endpoint and queue should never be unavailable, but where the queue processor might be).
The code
Relevant Job Code
// Models
use App\UptimeMonitor;
// Notifications
use App\Notifications\StatusQueue as StatusNotification;
class StatusQueue implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $monitor_source;
protected $monitor_data;
protected $monitor_repository;
/**
* Create a new job instance.
*
* #param string[] monitor_source (Eg: HetrixTools)
* #param array[] monitor_data (All data passed from the source)
*
*/
public function __construct($monitor_source, array $monitor_data)
{
$this->monitor_source = $monitor_source;
$this->monitor_data = $monitor_data;
if($this->monitor_source === 'centric')
return $this->centric();
}
/**
* Centric Uptime Monitoring
*/
public function centric()
{
$result = ($this->monitor_data['monitor_status'] == 'online') ? 'online' : 'timeout';
try {
$monitor = UptimeMonitor::where('identifier', '=', $this->monitor_data['monitor_id'])->firstOrFail();
$status = $monitor->status()->firstOrFail();
$contacts = $monitor->contacts()->get();
} catch (Exception $e) {
return Log::error('[JOBS::StatusQueue::centric] - ' . $e);
}
$status->state = $result;
if(!$contacts)
return true;
foreach($contacts as $contact) {
$contact->notify(new StatusNotification($monitor, $status));
}
}
}
Other code
If you do require any other code, let me know! This should however cover the entire functionality of the job class itself. Other than that, all that's happening is issuing that job - and how that's done is obvious based on the constructor.
Question
The final question from all of this: Why is this failing (as in; why can it not serialize the job, without needing the database information?) - and do you see a way to work around this issue, to where I do not need access to the database from Server C to queue the job from Server A, still using Laravel's Queue mechanics?
Much obliged, as always!
Turns out, the easiest solution is almost always the right one.
Server A does not need to have a replica of the job that Server B will process - it can have a completely empty job with the same class, and server B will still process it correctly.
As a result, this is now the Job on Server A:
class AlertQueue implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $job;
/**
* Create a new job instance.
*
*/
public function __construct()
{
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
//
}
}
Whilst Server B also has an AlertQueue job, which has all of the logic that will actually get performed.
I have a job in my Laravel project (v.4.2). which is used for inserting data into database. The class named "ProductUpdate". I use Amazon SQS for queue service.
What makes me confuse now is, when I changed the code in class "ProductUpdate",
it seems that the job is running by using old version of the class.
I even deleted all lines of code in the class but the jobs can still be able to run ( it stills inserts data).
Following is the job class.
The file of this class is at app/jobs/ProductUpdate.php
In my understanding, job class is the only place that will be called from queue, but why it can still be able to run when I deleted all the codes?
<?php
/**
* Here is a class to run a queued item sent from SQS
* Default method to use is fire()
**/
class ProductUpdate
{
public function fire($job, $data)
{
// Disable query log
DB::connection()->disableQueryLog();
// Set the job as a var so it will be used across functions
$this->job = $job;
$product = Product::find($productID);
if($product->product_type != 18) {
// Call the updater from library
$updater = App::make('Product\PriceUpdater');
$updater->update($product);
}
// Done and delete
$this->success();
}
private function success()
{
$this->job->delete();
}
private function fail($messages = array())
{
Log::error('Job processing fail', $messages);
$this->job->delete();
}
}
Your problem is related to cache.
Run this command in terminal to remove all cached data.
php artisan cache:clear
other way:-
Illuminate\Cache\FileStore has the function flush, so you can also use it:
Cache::flush();
This link will also help you :)
I'm trying to create some tests.
Here's my Test Class:
class ExampleTest extends TestCase {
public function setUp()
{
parent::setUp();
Artisan::call('migrate');
$this->seed();
Auth::loginUsingId(1);
}
public function testActionUpdateNew()
{
$action = new Action(Array());
$action->save();
var_dump($action->id);
Action::with('reponses','contact','user','etudiant','entreprise','etude')->findOrFail($action->id);
}
public function testEtudes()
{
$etudes=Etude::all()->toArray();
$this->assertCount(10, $etudes, "Nombre d'études incorrectes");
$numEtudes=count($etudes);
//Buggy part
$etude= Etude::create(Array());
var_dump($etude->id);
$etudes=Etude::all()->toArray();
$this->assertCount(11, $etudes, "Nombre d'études incorrectes");
//10+1 should equal to 11 but it hasnt updated
}
}
The test that is not passing is the second one: I count the number of eloquent Objects Etudes, which are of 10 at the beginning, I then add one etude to the database (using Etude::create()) , the object is created, because $etude->id gives out a real number. Howewer, the number of Etude hasn't updated.
The problem does go away when I remove the 'etude' from the eager loading in Action::with('reponses',...)
Here is the etudes relationship in the Action class:
public function etude() {
return $this->belongsTo('Etude');
}
Do you guys have any idea if eager-loading in laravel can have such strange behavior and how to fix that ?
EDIT
I found out that calling with('etude') had the action to remove the events registered to the Eloquent Model:
boot Method of Etude:
public static function boot()
{
parent::boot();
static::creating(function($etude)
{
var_dump("creating etude"); //This doesn't get executed even when I run Etude::create(Array());
}
);
}
So If I add Etude::boot() at the beginning of testEtudes, it works again. This is still strange.
Does eager loading has any effect on events or the boot method ? Or is the boot method not called automatically after each test ?
In Laravel tests, the event dispatcher is reset between each test, but the models are still only booted once as they live a pretty independent life. This means that between each test, the model listeners are erased but never re-registered. The solution is to not use boot() for registering model events, but rather but them in a separate file - either a service provider or a file included from app/start/global.php (app/events.php is a common one).