@@ -38,6 +38,7 @@ class Connection
38
38
39
39
private $ connection ;
40
40
private $ stream ;
41
+ private $ queue ;
41
42
private $ group ;
42
43
private $ consumer ;
43
44
private $ autoSetup ;
@@ -65,6 +66,7 @@ public function __construct(array $configuration, array $connectionCredentials =
65
66
$ this ->stream = $ configuration ['stream ' ] ?? self ::DEFAULT_OPTIONS ['stream ' ];
66
67
$ this ->group = $ configuration ['group ' ] ?? self ::DEFAULT_OPTIONS ['group ' ];
67
68
$ this ->consumer = $ configuration ['consumer ' ] ?? self ::DEFAULT_OPTIONS ['consumer ' ];
69
+ $ this ->queue = $ this ->stream .'__queue ' ;
68
70
$ this ->autoSetup = $ configuration ['auto_setup ' ] ?? self ::DEFAULT_OPTIONS ['auto_setup ' ];
69
71
$ this ->maxEntries = $ configuration ['stream_max_entries ' ] ?? self ::DEFAULT_OPTIONS ['stream_max_entries ' ];
70
72
}
@@ -125,6 +127,34 @@ public function get(): ?array
125
127
$ this ->setup ();
126
128
}
127
129
130
+ try {
131
+ $ queuedMessageCount = $ this ->connection ->zcount ($ this ->queue , 0 , $ this ->getCurrentTimeInMilliseconds ());
132
+ } catch (\RedisException $ e ) {
133
+ throw new TransportException ($ e ->getMessage (), 0 , $ e );
134
+ }
135
+
136
+ if ($ queuedMessageCount ) {
137
+ for ($ i = 0 ; $ i < $ queuedMessageCount ; ++$ i ) {
138
+ try {
139
+ $ queuedMessages = $ this ->connection ->zpopmin ($ this ->queue , 1 );
140
+ } catch (\RedisException $ e ) {
141
+ throw new TransportException ($ e ->getMessage (), 0 , $ e );
142
+ }
143
+
144
+ foreach ($ queuedMessages as $ queuedMessage => $ time ) {
145
+ $ queuedMessage = json_decode ($ queuedMessage , true );
146
+ // if a futured placed message is actually popped because of a race condition with
147
+ // another running message consumer, the message is readded to the queue by add function
148
+ // else its just added stream and will be available for all stream consumers
149
+ $ this ->add (
150
+ $ queuedMessage ['body ' ],
151
+ $ queuedMessage ['headers ' ],
152
+ $ time - $ this ->getCurrentTimeInMilliseconds ()
153
+ );
154
+ }
155
+ }
156
+ }
157
+
128
158
$ messageId = '> ' ; // will receive new messages
129
159
130
160
if ($ this ->couldHavePendingMessages ) {
@@ -203,24 +233,40 @@ public function reject(string $id): void
203
233
}
204
234
}
205
235
206
- public function add (string $ body , array $ headers ): void
236
+ public function add (string $ body , array $ headers, int $ delayInMs = 0 ): void
207
237
{
208
238
if ($ this ->autoSetup ) {
209
239
$ this ->setup ();
210
240
}
211
241
212
242
try {
213
- if ($ this ->maxEntries ) {
214
- $ added = $ this ->connection ->xadd ($ this ->stream , '* ' , ['message ' => json_encode (
215
- ['body ' => $ body , 'headers ' => $ headers ]
216
- )], $ this ->maxEntries , true );
243
+ if ($ delayInMs > 0 ) { // the delay could be smaller 0 in a queued message
244
+ $ message = json_encode ([
245
+ 'body ' => $ body ,
246
+ 'headers ' => $ headers ,
247
+ // Entry need to be unique in the sorted set else it would only be added once to the delayed messages queue
248
+ 'uniqid ' => uniqid ('' , true ),
249
+ ]);
250
+
251
+ $ score = (int ) ($ this ->getCurrentTimeInMilliseconds () + $ delayInMs );
252
+ $ added = $ this ->connection ->zadd ($ this ->queue , ['NX ' ], $ score , $ message );
217
253
} else {
218
- $ added = $ this ->connection ->xadd ($ this ->stream , '* ' , ['message ' => json_encode (
219
- ['body ' => $ body , 'headers ' => $ headers ]
220
- )]);
254
+ $ message = json_encode ([
255
+ 'body ' => $ body ,
256
+ 'headers ' => $ headers ,
257
+ ]);
258
+
259
+ if ($ this ->maxEntries ) {
260
+ $ added = $ this ->connection ->xadd ($ this ->stream , '* ' , ['message ' => $ message ], $ this ->maxEntries , true );
261
+ } else {
262
+ $ added = $ this ->connection ->xadd ($ this ->stream , '* ' , ['message ' => $ message ]);
263
+ }
221
264
}
222
265
} catch (\RedisException $ e ) {
223
- throw new TransportException ($ e ->getMessage (), 0 , $ e );
266
+ if ($ error = $ this ->connection ->getLastError () ?: null ) {
267
+ $ this ->connection ->clearLastError ();
268
+ }
269
+ throw new TransportException ($ error ?? $ e ->getMessage (), 0 , $ e );
224
270
}
225
271
226
272
if (!$ added ) {
@@ -246,4 +292,15 @@ public function setup(): void
246
292
247
293
$ this ->autoSetup = false ;
248
294
}
295
+
296
+ private function getCurrentTimeInMilliseconds (): int
297
+ {
298
+ return (int ) (microtime (true ) * 1000 );
299
+ }
300
+
301
+ public function cleanup (): void
302
+ {
303
+ $ this ->connection ->del ($ this ->stream );
304
+ $ this ->connection ->del ($ this ->queue );
305
+ }
249
306
}
0 commit comments