1
1
<?php
2
2
3
+ declare (strict_types=1 );
4
+
3
5
namespace iamfarhad \LaravelRabbitMQ \Console ;
4
6
5
7
use Symfony \Component \Console \Attribute \AsCommand ;
6
8
use Illuminate \Queue \Console \WorkCommand ;
7
9
use Illuminate \Support \Str ;
10
+ use RuntimeException ;
8
11
9
12
#[AsCommand(name: 'rabbitmq:consume ' )]
10
13
final class ConsumeCommand extends WorkCommand
11
14
{
15
+ /**
16
+ * The console command signature.
17
+ *
18
+ * @var string
19
+ */
12
20
protected $ signature = 'rabbitmq:consume
13
21
{connection? : The name of the queue connection to work}
14
22
{--name=default : The name of the consumer}
15
23
{--queue= : The names of the queues to work}
16
24
{--once : Only process the next job on the queue}
17
25
{--stop-when-empty : Stop when the queue is empty}
18
- {--delay=0 : The number of seconds to delay failed jobs (Deprecated) }
26
+ {--delay=0 : The number of seconds to delay failed jobs}
19
27
{--backoff=0 : The number of seconds to wait before retrying a job that encountered an uncaught exception}
20
28
{--max-jobs=0 : The number of jobs to process before stopping}
21
29
{--max-time=0 : The maximum number of seconds the worker should run}
@@ -25,69 +33,143 @@ final class ConsumeCommand extends WorkCommand
25
33
{--timeout=60 : The number of seconds a child process can run}
26
34
{--tries=1 : Number of times to attempt a job before logging it failed}
27
35
{--rest=0 : Number of seconds to rest between jobs}
28
- {--max-priority=}
36
+ {--max-priority=null : Maximum priority level to consume }
29
37
{--consumer-tag}
30
38
{--prefetch-size=0}
31
39
{--prefetch-count=1000}
32
40
{--num-processes=2 : Number of processes to run in parallel}
33
41
' ;
34
42
35
- protected $ description = 'Consume messages ' ;
36
-
37
- public function handle (): int |null
43
+ /**
44
+ * The console command description.
45
+ *
46
+ * @var string
47
+ */
48
+ protected $ description = 'Consume messages from RabbitMQ queue ' ;
49
+
50
+ /**
51
+ * Execute the console command.
52
+ */
53
+ public function handle (): int
38
54
{
39
- $ numProcesses = $ this ->option ('num-processes ' );
55
+ $ numProcesses = (int ) $ this ->option ('num-processes ' );
56
+
57
+ if ($ numProcesses < 1 ) {
58
+ $ this ->error ('Number of processes must be at least 1 ' );
59
+ return 1 ;
60
+ }
61
+
62
+ // Skip forking if only one process is needed
63
+ if ($ numProcesses === 1 ) {
64
+ return $ this ->consume ();
65
+ }
66
+
67
+ // Check if pcntl extension is available
68
+ if (!extension_loaded ('pcntl ' )) {
69
+ $ this ->error ('The pcntl extension is required for parallel processing ' );
70
+ return 1 ;
71
+ }
72
+
73
+ $ childPids = [];
40
74
41
75
for ($ i = 0 ; $ i < $ numProcesses ; $ i ++) {
42
76
$ pid = pcntl_fork ();
43
77
44
78
if ($ pid === -1 ) {
45
- // Error handling
46
- echo "Could not fork process \n" ;
47
- exit (1 );
79
+ $ this ->error ("Failed to fork process $ i " );
80
+ continue ;
48
81
}
49
82
50
83
if ($ pid === 0 ) {
51
- // This is the child process
52
- $ this ->consume ();
53
- exit (0 );
84
+ // Child process
85
+ exit ($ this ->consume ());
86
+ } else {
87
+ // Parent process
88
+ $ childPids [] = $ pid ;
89
+ $ this ->info ("Started worker process $ pid " );
54
90
}
55
91
}
56
92
93
+ // Set up signal handling for graceful termination
94
+ if (function_exists ('pcntl_signal ' )) {
95
+ pcntl_signal (SIGTERM , function () use (&$ childPids ) {
96
+ foreach ($ childPids as $ pid ) {
97
+ if (function_exists ('posix_kill ' )) {
98
+ posix_kill ($ pid , SIGTERM );
99
+ }
100
+ }
101
+ });
102
+ }
103
+
57
104
// Wait for all child processes to finish
58
- // while (pcntl_waitpid(0, $status) !== -1) {
59
- // Handle exit status if needed
60
- // }
105
+ foreach ($ childPids as $ pid ) {
106
+ pcntl_waitpid ($ pid , $ status );
107
+
108
+ if (pcntl_wifexited ($ status )) {
109
+ $ exitCode = pcntl_wexitstatus ($ status );
110
+ if ($ exitCode !== 0 ) {
111
+ $ this ->warn ("Process $ pid exited with code $ exitCode " );
112
+ }
113
+ } else {
114
+ $ this ->warn ("Process $ pid terminated abnormally " );
115
+ }
116
+ }
61
117
62
118
return 0 ;
63
119
}
64
120
65
- private function consume (): void
121
+ /**
122
+ * Configure and run the consumer.
123
+ */
124
+ private function consume (): int
66
125
{
67
- $ consumer = $ this ->worker ;
126
+ try {
127
+ $ consumer = $ this ->worker ;
128
+
129
+ if (!$ consumer ) {
130
+ throw new RuntimeException ('Worker instance not initialized ' );
131
+ }
68
132
69
- $ consumer ->setContainer ($ this ->laravel );
70
- $ consumer ->setName ($ this ->option ('name ' ));
71
- $ consumer ->setConsumerTag ($ this ->consumerTag ());
72
- $ consumer ->setMaxPriority ((int ) $ this ->option ('max-priority ' ));
73
- $ consumer ->setPrefetchSize ((int ) $ this ->option ('prefetch-size ' ));
74
- $ consumer ->setPrefetchCount ((int ) $ this ->option ('prefetch-count ' ));
133
+ $ consumer ->setContainer ($ this ->laravel );
134
+ $ consumer ->setName ($ this ->option ('name ' ));
135
+ $ consumer ->setConsumerTag ($ this ->generateConsumerTag ());
75
136
76
- parent ::handle ();
137
+ // Initialize prefetch size and count first
138
+ $ consumer ->setPrefetchSize ((int ) $ this ->option ('prefetch-size ' ));
139
+ $ consumer ->setPrefetchCount ((int ) $ this ->option ('prefetch-count ' ));
140
+
141
+ // Only set max priority if it's provided and not null
142
+ $ maxPriority = $ this ->option ('max-priority ' );
143
+ if ($ maxPriority !== null && $ maxPriority !== '' ) {
144
+ $ consumer ->setMaxPriority ((int ) $ maxPriority );
145
+ }
146
+
147
+ return parent ::handle () ?? 0 ;
148
+ } catch (\Throwable $ e ) {
149
+ $ this ->error ($ e ->getMessage ());
150
+ return 1 ;
151
+ }
77
152
}
78
153
79
- private function consumerTag (): string
154
+ /**
155
+ * Generate a unique consumer tag.
156
+ */
157
+ private function generateConsumerTag (): string
80
158
{
81
159
if ($ consumerTag = $ this ->option ('consumer-tag ' )) {
82
160
return $ consumerTag ;
83
161
}
84
162
163
+ $ appName = config ('app.name ' , 'laravel ' );
164
+ $ consumerName = $ this ->option ('name ' );
165
+ $ uniqueId = md5 (serialize ($ this ->options ()) . Str::random (16 ) . getmypid ());
166
+
85
167
$ consumerTag = implode (
86
168
'_ ' ,
87
169
[
88
- Str::slug (config ( ' app.name ' , ' laravel ' ) ),
89
- Str::slug ($ this -> option ( ' name ' ) ),
90
- md5 ( serialize ( $ this -> options ()) . Str:: random ( 16 ) . getmypid ()) ,
170
+ Str::slug ($ appName ),
171
+ Str::slug ($ consumerName ),
172
+ $ uniqueId ,
91
173
]
92
174
);
93
175
0 commit comments