├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── queue-watch.php └── src ├── Commands └── QueueWatchCommand.php └── QueueWatchServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `queue-watch` will be documented in this file. 4 | 5 | ## Initial Release - 2024-09-19 6 | 7 | This is first release 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Rajen Trivedi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automating Laravel Queue Worker Restarts 2 | ![Alt text](art/queue-watch-1.webp?raw=true "Title") 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/rajentrivedi/queue-watch.svg?style=flat-square)](https://packagist.org/packages/rajentrivedi/queue-watch) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/rajentrivedi/queue-watch/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/rajentrivedi/queue-watch/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/rajentrivedi/queue-watch/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/rajentrivedi/queue-watch/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/rajentrivedi/queue-watch.svg?style=flat-square)](https://packagist.org/packages/rajentrivedi/queue-watch) 7 | 8 | ## Supported Versions 9 | | Version| Supported | 10 | | -------| ------------------ | 11 | | 10.x | :white_check_mark: | 12 | | 11.x | :white_check_mark: | 13 | 14 | Managing queue workers in a Laravel application can sometimes be tedious, especially when dealing with long-running processes. A common challenge is ensuring that workers are restarted whenever there are changes in the jobs, events, or listeners folders. Restarting workers manually can be inefficient and prone to oversight specially during development, potentially leading to application inconsistencies or stale queue processing. 15 | 16 | To solve this problem, I’ve developed a Laravel package that automates this process. This package detects file changes within your Laravel application’s jobs, events, and listeners folders and automatically restarts the queue worker when changes are detected. 17 | 18 | ## Support us 19 | 20 | 25 | 26 | ## Installation 27 | 28 | You can install the package via composer: 29 | 30 | ```bash 31 | composer require rajentrivedi/queue-watch --dev 32 | ``` 33 | 34 | You can publish the config file with: 35 | 36 | ```bash 37 | php artisan vendor:publish --tag="queue-watch-config" 38 | ``` 39 | 40 | This is the contents of the published config file: 41 | 42 | ```php 43 | return [ 44 | 'directories' => [ 45 | app_path('Jobs'), 46 | app_path('Events'), 47 | app_path('Listeners'), 48 | ], 49 | ``` 50 | ## Usage 51 | 52 | ```php 53 | php artisan queue:work:watch 54 | ``` 55 | 56 | ## Testing 57 | 58 | ```bash 59 | composer test 60 | ``` 61 | 62 | ## Changelog 63 | 64 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 65 | 66 | ## Contributing 67 | 68 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 69 | 70 | ## Security Vulnerabilities 71 | 72 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 73 | 74 | ## Credits 75 | 76 | - [Rajen Trivedi](https://github.com/69707769+rajentrivedi) 77 | - [All Contributors](../../contributors) 78 | 79 | ## License 80 | 81 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 82 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rajentrivedi/queue-watch", 3 | "description": "watch jobs, listeners and events folders for changes in files", 4 | "keywords": [ 5 | "Rajen Trivedi", 6 | "laravel", 7 | "queue-watch", 8 | "queue", 9 | "laravel queue", 10 | "laravel queue watch", 11 | "events", 12 | "listeners", 13 | "jobs", 14 | "queue worker", 15 | "job monitoring", 16 | "filesystem watcher", 17 | "watcher", 18 | "process automation", 19 | "continuous queue processing" 20 | ], 21 | "homepage": "https://github.com/rajentrivedi/queue-watch", 22 | "license": "MIT", 23 | "authors": [ 24 | { 25 | "name": "Rajen Trivedi", 26 | "email": "laravel.rajen@gmail.com", 27 | "role": "Developer" 28 | } 29 | ], 30 | "require": { 31 | "php": "^8.2", 32 | "spatie/laravel-package-tools": "^1.16", 33 | "illuminate/contracts": "^10.0||^11.0" 34 | }, 35 | "require-dev": { 36 | "laravel/pint": "^1.14", 37 | "nunomaduro/collision": "^8.1.1||^7.10.0", 38 | "larastan/larastan": "^2.9", 39 | "orchestra/testbench": "^9.0.0||^8.22.0", 40 | "pestphp/pest": "^2.34", 41 | "pestphp/pest-plugin-arch": "^2.7", 42 | "pestphp/pest-plugin-laravel": "^2.3", 43 | "phpstan/extension-installer": "^1.3", 44 | "phpstan/phpstan-deprecation-rules": "^1.1", 45 | "phpstan/phpstan-phpunit": "^1.3", 46 | "spatie/laravel-ray": "^1.35" 47 | }, 48 | "autoload": { 49 | "psr-4": { 50 | "QueueWatch\\QueueWatch\\": "src/", 51 | "QueueWatch\\QueueWatch\\Database\\Factories\\": "database/factories/" 52 | } 53 | }, 54 | "autoload-dev": { 55 | "psr-4": { 56 | "QueueWatch\\QueueWatch\\Tests\\": "tests/", 57 | "Workbench\\App\\": "workbench/app/" 58 | } 59 | }, 60 | "scripts": { 61 | "post-autoload-dump": "@composer run prepare", 62 | "clear": "@php vendor/bin/testbench package:purge-queue-watch --ansi", 63 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 64 | "build": [ 65 | "@composer run prepare", 66 | "@php vendor/bin/testbench workbench:build --ansi" 67 | ], 68 | "start": [ 69 | "Composer\\Config::disableProcessTimeout", 70 | "@composer run build", 71 | "@php vendor/bin/testbench serve" 72 | ], 73 | "analyse": "vendor/bin/phpstan analyse", 74 | "test": "vendor/bin/pest", 75 | "test-coverage": "vendor/bin/pest --coverage", 76 | "format": "vendor/bin/pint" 77 | }, 78 | "config": { 79 | "sort-packages": true, 80 | "allow-plugins": { 81 | "pestphp/pest-plugin": true, 82 | "phpstan/extension-installer": true 83 | } 84 | }, 85 | "extra": { 86 | "laravel": { 87 | "providers": [ 88 | "QueueWatch\\QueueWatch\\QueueWatchServiceProvider" 89 | ], 90 | "aliases": { 91 | "QueueWatch": "QueueWatch\\QueueWatch\\Facades\\QueueWatch" 92 | } 93 | } 94 | }, 95 | "minimum-stability": "stable", 96 | "prefer-stable": true 97 | } 98 | -------------------------------------------------------------------------------- /config/queue-watch.php: -------------------------------------------------------------------------------- 1 | [ 6 | app_path('Jobs'), 7 | app_path('Events'), 8 | app_path('Listeners'), 9 | ], 10 | ]; 11 | -------------------------------------------------------------------------------- /src/Commands/QueueWatchCommand.php: -------------------------------------------------------------------------------- 1 | startQueueWorker(); 34 | 35 | $finder = new Finder; 36 | $directories = $this->getWatchDirectories(); 37 | 38 | if (empty($directories)) { 39 | $this->error('No directories to watch. The queue worker will run without file watching.'); 40 | $this->monitorQueueWorker(); 41 | 42 | return; 43 | } 44 | 45 | $finder->files()->in($directories)->name('*.php'); 46 | 47 | $lastModified = $this->getLastModifiedTime($finder); 48 | 49 | /** @phpstan-ignore-next-line */ 50 | while (true) { 51 | usleep(1000000); 52 | 53 | clearstatcache(); 54 | 55 | $currentLastModified = $this->getLastModifiedTime($finder); 56 | 57 | if ($currentLastModified > $lastModified) { 58 | $this->info('File save detected. Restarting queue worker...'); 59 | $this->stopQueueWorker(); 60 | $this->startQueueWorker(); 61 | $lastModified = $currentLastModified; 62 | } 63 | 64 | $this->monitorQueueWorker(); 65 | } 66 | } 67 | 68 | protected function monitorQueueWorker() 69 | { 70 | if (! $this->process->isRunning()) { 71 | $this->error('Queue worker stopped unexpectedly. Restarting...'); 72 | $this->startQueueWorker(); 73 | } 74 | } 75 | 76 | protected function getWatchDirectories() 77 | { 78 | $directories = []; 79 | $possibleDirectories = [ 80 | app_path('Jobs'), 81 | app_path('Events'), 82 | app_path('Listeners'), 83 | ]; 84 | 85 | $possibleDirectories = config('queue-watch.directories') ?? $possibleDirectories; 86 | $possibleDirectories[] = base_path('config'); 87 | 88 | foreach ($possibleDirectories as $dir) { 89 | if (is_dir($dir)) { 90 | $directories[] = $dir; 91 | } 92 | } 93 | 94 | return $directories; 95 | } 96 | 97 | protected function sleep($seconds) 98 | { 99 | sleep($seconds); 100 | } 101 | 102 | protected function startQueueWorker() 103 | { 104 | $command = ['php', 'artisan', 'queue:work']; 105 | 106 | $command = array_merge($command, $this->getQueueWorkArguments()); 107 | 108 | $this->process = new Process($command); 109 | $this->process->setPty(true); 110 | $this->process->start(function ($type, $buffer) { 111 | $this->info($buffer); 112 | }); 113 | 114 | $this->info('Queue worker started.'); 115 | } 116 | 117 | protected function stopQueueWorker() 118 | { 119 | if ($this->process && $this->process->isRunning()) { 120 | $this->process->stop(); 121 | $this->info('Queue worker stopped.'); 122 | } 123 | } 124 | 125 | protected function getQueueWorkArguments() 126 | { 127 | $args = []; 128 | 129 | if ($connection = $this->argument('connection')) { 130 | $args[] = $connection; 131 | } 132 | 133 | $options = $this->options(); 134 | if (is_array($options)) { 135 | foreach ($options as $key => $value) { 136 | if ($value === true) { 137 | $args[] = "--{$key}"; 138 | } elseif ($value !== false && $value !== null) { 139 | $args[] = "--{$key}={$value}"; 140 | } 141 | } 142 | } 143 | 144 | $args[] = '--verbose'; 145 | 146 | return $args; 147 | } 148 | 149 | protected function getLastModifiedTime(Finder $finder) 150 | { 151 | $lastModified = 0; 152 | foreach ($finder as $file) { 153 | $lastModified = max($lastModified, $file->getMTime()); // Get the most recent modification time 154 | } 155 | 156 | return $lastModified; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/QueueWatchServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('queue-watch') 20 | ->hasConfigFile() 21 | ->hasCommand(QueueWatchCommand::class); 22 | } 23 | } 24 | --------------------------------------------------------------------------------