Framework: ModelNotFoundException on Laravel Job with SerializeModels...But fixed with delay!!!

Created on 9 Apr 2019  路  7Comments  路  Source: laravel/framework

  • Laravel Version: 5.8.9
  • PHP Version: 7.2.3
  • Database Driver & Version: MySql 8

Description:

I've read the entire internet about the
Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Ticket] raised by a failed job that uses SerializeModelstrait.

The strange thing is that ModelNotFoundException is raised even if the Model 100% exists on the database. I tried to deserialize the payloadfrom the failed_jobstable and search one-by-one the primary keys and all exist in the database. There is a transaction in my code and AFTER it commits i raise some events that fires the same job. No way that the PK of Eloquent Models to be null because i do not create new models inside a transaction but i use existing models.

  1. If i retry to run the failed_jobs at a later time from the console, jobs all executing successfully. No ModelNotFoundException exception
  2. If i add a delay on the job, job is executing successfully. No ModelNotFoundException exception
  3. If i remove the delay, job fails with ModelNotFoundException from __wakeup

Steps To Reproduce:

I setup a listener for some events and every time i dispatch a job. The parameter for the job is a class that implements INotificationEvent interface.

image

needs more info

Most helpful comment

Are you dispatching the job within a transaction? I had a similar issue where I was dispatching an event inside a transaction which hadn't been commited yet when the queue picked up the event.

All 7 comments

Does it work if you just pass the id to the job and refetch the model there in?

Are you dispatching the job within a transaction? I had a similar issue where I was dispatching an event inside a transaction which hadn't been commited yet when the queue picked up the event.

Closing this issue because it's inactive, already solved, old or not relevant anymore. Feel free to reply if you're still experiencing this issue.

Sorry for the delay....

This is the job
image

and the contrsuctor parameter INotificationEvent $event is an object that holds existing eloquent models.

From the code of SerializeModels i understand that when the job is queued in Redis is serialized and that is:

  • Every Property of INotificationEvent $event is traversed and serialized. If one or more properties are Eloquent Models are serialized in a special way as new ModelIdentifier.

In the deserialization process although ModelIdentifier points to an existing Id, without the delay at the job always ModelNotFoundException raised.

I am raising events and dispatch jobs after i commit the transaction. But either way, the models are not new...are just models that already existed in the database

I am asking if there is a special case where a model deserialization might not work as expected.

So what I believe is happening is that you're trying to serialize an object containing models. This isn't wanted I believe with the SerializeModels trait. I think it only works if you try to serialize models directly as a property on the job. If you want to serialize other objects I believe you should only use scalars like model ids and retrieve them yourselves.

Btw, the hint is in the trait's name. It only serializes models and no other objects.

This is all the classes involved a complete example

interface INotificationEvent
{
    public function getTicket();
    public function getUser();
}

class NotificationEventBase extends Event
{
    public $ticket = null; //Eloquent Model
    public $user   = null; //Eloquent Model

    public function __construct() {

    }

    public function load(int $ticket_id, int $user_id)
    {
        $this->ticket=loadTicketFromDb($ticket_id);
        $this->user=loadUserFromDb($user_id);
    }

    public function getUser() {
        return $this->user;
    }

    public function getTicket() {
        return $this->ticket;
    }
}

class TicketCreated extends NotificationEventBase implements INotificationEvent
{
    use SerializesModels;

    public function __construct(int $ticket_id, int $user_id) {
        parent::load($ticket_id, $user_id);
    }
}

class NotificationEngineJob implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    public $payload = null;

    public function __construct(INotificationEvent $payload) {
        $this->payload = $payload;
    }

    public function handle() {
        //Somw code to handle
    }
}

///*******************************************************
///------------------ Finally --------------------------//
///*******************************************************
$data=new TicketCreated(1,2);
$job=(new NotificationEngineJob($data))->delay(60);
dispatch($job);






This does still seem to exist as of v7.28.3.

I dispatch a job from a model observer's created event that accepts the newly created model. If I only create a few models, the jobs dispatch fine. Running code that creates 100s of models which dispatches 100s of jobs using the new models is where the problem occurs.

The jobs all run fine if I retry from Horizon so I don't believe it's a code problem.

#1 /home/forge/site.com/vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php(57): App\Jobs\SendWebHook->restoreModel()

The error occurs during restore which leads me to believe the new models aren't available in the database yet.

namespace App\Jobs;

use App\Models\Webhook;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;

class SendWebHook implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $tries = 0;
    public $maxExceptions = 3;

    protected Model $model;
    protected string $event;
    protected ?array $changed_from;
    protected ?array $changed_to;

    /**
     * Create a new job instance.
     *
     * @param Model $model
     * @param string $event
     * @param array $changed_from
     * @param array $changed_to
     */
    public function __construct(Model $model, string $event, ?array $changed_from, ?array $changed_to)
    {
        $this->model = $model;
        $this->event = $event;
        $this->changed_from = $changed_from;
        $this->changed_to = $changed_to;
    }

    public function retryUntil()
    {
        return now()->addHours(1);
    }

  public function handle() 
  {
     \Log::info(json_encode($this->model->getAttributes());
  }

}

The failure isn鈥檛 a problem, it should fail if the model isn鈥檛 available. The problem I see is the jobs don鈥檛 retry automatically as they should with $maxExceptions 3 and retryUntil() 1 hour from now. It seems the model not found exception automatically fails jobs?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SachinAgarwal1337 picture SachinAgarwal1337  路  3Comments

JamborJan picture JamborJan  路  3Comments

ghost picture ghost  路  3Comments

gabriellimo picture gabriellimo  路  3Comments

fideloper picture fideloper  路  3Comments