AtroCore includes a powerful background job system that allows you to execute long-running or resource-intensive tasks asynchronously. This system helps improve application responsiveness by offloading heavy operations to be processed in the background.
Jobs in AtroCore follow a lifecycle (Pending → Running → Success/Failed) and are managed by a dedicated job processing manager that runs via cron.
Creating a Job
To implement a background job, follow these steps:
1. Create a Job Class
Create a new PHP class in the Jobs
directory of your module. The class should implement the \Atro\Jobs\JobInterface
and may extend the \Atro\Jobs\AbstractJob
class (recommended for access to common services).
Example: Jobs/ExampleJob.php
<?php
declare(strict_types=1);
namespace YourModule\Jobs;
use Atro\Jobs\AbstractJob;
use Atro\Jobs\JobInterface;
use Atro\Entities\Job;
use Espo\Core\Exceptions\Error;
class ExampleJob extends AbstractJob implements JobInterface
{
/**
* Execute the job logic
*
* @param Job $job The job entity with metadata and payload
*
* @throws Error When something goes wrong
*/
public function run(Job $job): void
{
// Access job payload data
$payload = $job->getPayload();
$data1 = $payload['data1'] ?? null;
// Access services via AbstractJob parent methods
$entityManager = $this->getEntityManager();
$metadata = $this->getMetadata();
// Implement your job logic here
// Log information for debugging
$this->getContainer()->get('log')->debug('ExampleJob: Processing completed');
}
}
The AbstractJob
parent class provides access to commonly used container services:
- Entity Manager
- Metadata
- Event Manager
- Service Factory
- Config
- Language
- Memory Storage
- Twig engine
- Container (for other container services)
2. Register the Job Type
Register your job in the module's metadata by creating/updating the file Resources/metadata/app/jobTypes.json
:
{
"ExampleJob": {
"scheduledJob": true,
"handler": "\\YourModule\\Jobs\\ExampleJob"
}
}
Properties:
scheduledJob
: Whentrue
, this job type becomes available for scheduling in the Admin panelhandler
: The fully qualified namespace of your job class
When registering new job types, choose unique names carefully. If you register a job with the same name as an existing job type (either from core or another module), your implementation will override the original one. This can lead to unexpected behavior, broken functionality, or system instability.
Following these naming conventions helps prevent conflicts and makes your code more safe.
Dispatching Jobs
To enqueue a job for background processing:
/** @var \Espo\Core\ORM\EntityManager $entityManager */
$entityManager = $container->get('entityManager');
// Create a job entity
$jobEntity = $entityManager->getEntity('Job');
$jobEntity->set([
'name' => "Process data for product X", // Human-readable description
'type' => 'ExampleJob', // Job type name from metadata
'priority' => 100, // Optional, Higher number = higher priority
'payload' => [ // Data passed into the job
"productId" => "123abc",
"userId" => "user-456",
"options" => [
"processImages" => true,
"updateRelated" => true
]
]
]);
// Save to queue the job
$entityManager->saveEntity($jobEntity);
Once saved, the job is added to the queue with Pending
status. The job manager will pick it up during the next cron
run.
Job Lifecycle and States
Jobs in AtroCore can have the following states:
State | Description |
---|---|
Pending | Newly created job waiting to be processed |
Running | Currently being executed by the job manager |
Success | Successfully completed |
Failed | Execution failed (error details are stored in the message field) |
Canceled | Manually canceled before completion |
The job manager automatically handles state transitions. When a job fails, detailed error information is stored in the
job's message
field for troubleshooting.
Canceling Jobs
You can cancel a job at any time by simply updating its status to Canceled
. This is useful when you need to stop a
pending job before it starts processing or to signal a running job that it should stop (if the job implementation checks
for cancellation).
Example: Canceling a Job
/** @var \Espo\Core\ORM\EntityManager $entityManager */
$entityManager = $container->get('entityManager');
// Get the job you want to cancel
$jobId = 'some-id';
$job = $entityManager->getEntity('Job', $jobId);
// Update status to Canceled
$job->set('status', 'Canceled');
$entityManager->saveEntity($job);
Accessing Job Results and Error Information
To check the status or error details of a job:
$jobId = '12345'; // ID of the previously created job
$job = $entityManager->getEntity('Job', $jobId);
$status = $job->get('status'); // 'Pending', 'Running', 'Success', 'Failed', 'Canceled'
// Process ID for running jobs
if ($job->get('status') === 'Running') {
$pid = $job->get('pid'); // Linux process ID of the running job
echo "Job is running as process ID: " . $pid;
}
// For failed jobs, check the error message
if ($job->get('status') === 'Failed') {
$errorMessage = $job->get('message'); // Contains detailed error information
echo "Job failed: " . $errorMessage;
}
Key job information fields:
status
: Current state of the jobmessage
: Error details when a job failspid
: Linux process ID of the job when it's in the "Running" state
The pid
field is particularly useful for system administrators when troubleshooting or monitoring job execution. It
allows correlating AtroCore jobs with actual system processes, which can help identify resource usage issues or
terminate stuck processes if necessary.
When a job encounters an exception or error during execution, the job manager automatically:
- Sets the job status to
Failed
- Captures the exception message
- Stores this information in the job's
message
field
This makes it easy to diagnose and fix issues with failed jobs by examining the error message directly in the job record.
Concurrent Job Processing
AtroCore's job manager supports parallel processing of multiple jobs simultaneously to maximize throughput and efficiency. This is especially beneficial for systems with many jobs or when processing needs to scale.
Worker Configuration
The job processing system can run multiple concurrent worker processes, each handling one job at a time. The number of concurrent workers is configurable via the maxConcurrentWorkers
parameter in your configuration:
// In data/config.php
return [
// other config
'maxConcurrentWorkers' => 10, // Set to run 10 job workers in parallel
// other config
];
Worker Configuration Guidelines:
- Default value: 6 concurrent workers
- Minimum value: 4 workers (enforced by the system)
- Maximum value: 50 workers (enforced by the system)
- Recommended setting: Adjust based on your server resources and typical job workload
Performance Considerations
When setting the number of concurrent workers, consider:
- CPU resources: Each worker consumes CPU time. Too many workers on a system with limited CPU can degrade performance.
- Memory usage: More workers require more RAM. Monitor memory usage to prevent swapping.
- Job characteristics: CPU-bound jobs benefit less from high concurrency than I/O-bound jobs.
- Server load: On shared hosting or multi-tenant environments, be mindful of overall system impact.
For most production environments, a value between 8-12 workers provides good throughput without excessive resource consumption.
Job Processing Manager
Pending jobs in the queue are processed based on priority via the job manager daemon, which is a background process that is always active. The job manager daemon is started via cron. Ensure that the cron task is properly configured on your server.
* * * * * www-data php /path_to_atrocore/console.php cron
To verify the cron configuration (if you have server access):
sudo -u www-data crontab -e
This should show the AtroCore cron command.
Manual Job Execution
For testing or troubleshooting, you can manually execute a job regardless of its current state:
php console.php job <jobId> --run
or in debug mode
php console.php job debug_<jobId> --run
In debug mode, the job is executed but the status is not updated. This is useful for debugging failed jobs.
Scheduled Jobs
Jobs marked with "scheduledJob": true
in metadata can be scheduled to run periodically via the Admin panel:
- Navigate to Administration → Scheduled Jobs
- Create a new scheduled job and select your job type
- Configure the execution schedule (cron syntax)
For more information about configuring scheduled jobs, see the Scheduled Jobs documentation.
Best Practices
- Job Granularity: Design jobs to be as granular as possible, focusing on specific tasks rather than complex workflows
- Error Handling: Implement comprehensive error handling in your job's
run()
method - Resource Consideration: Be mindful of resource usage, especially for jobs that process large datasets
- Transaction Management: Use database transactions where appropriate to ensure data consistency
- Logging: Add debug logs to help with troubleshooting
- Timeouts: Be aware of potential timeouts for very long-running jobs
- Payload Size: Keep job payloads reasonably small; store large data in temporary storage if needed
- Error Inspection: Always check the
message
field of failed jobs to understand what went wrong - Process Monitoring: Use the
pid
field to monitor system resources for long-running jobs
Troubleshooting
- Job never executes: Verify that the cron task is running correctly
- Job fails immediately: Check the job's
message
field for detailed error information - Job gets stuck in "Running": Check the process with the ID from the
pid
field to see if it's still active - Slow job execution: Consider optimization or breaking into smaller jobs
- High memory usage: Use the
pid
to monitor resource consumption and implement batch processing - Recurring failures: Examine error patterns in the
message
field of failed jobs to identify systemic issues - Emergency termination: For truly stuck jobs, you can use the Linux
kill
command with the job'spid
(use with caution)
With the job system, you can build powerful background processing capabilities while maintaining responsive user interfaces for your AtroCore applications.