Skip to content

Commit 5ca13f5

Browse files
committed
Merge remote-tracking branch 'origin/master' into multi-client-configuration
2 parents d5c497c + 1f96246 commit 5ca13f5

15 files changed

Lines changed: 234 additions & 164 deletions

‎README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,19 @@ Features:
3030
* Adopts [queue interoperable](https://github.com/queue-interop/queue-interop) interfaces (inspired by [Java JMS](https://docs.oracle.com/javaee/7/api/javax/jms/package-summary.html)).
3131
* Battle-tested. Used in production.
3232
* Supported transports
33-
* [AMQP(s)](docs/transport/amqp.md) based on [PHP AMQP extension](https://github.com/pdezwart/php-amqp).
33+
* [AMQP(s)](docs/transport/amqp.md) based on [PHP AMQP extension](https://github.com/pdezwart/php-amqp)
3434
[![Build Status](https://travis-ci.org/php-enqueue/amqp-ext.png?branch=master)](https://travis-ci.org/php-enqueue/amqp-ext)
3535
[![Total Downloads](https://poser.pugx.org/enqueue/amqp-ext/d/total.png)](https://packagist.org/packages/enqueue/amqp-ext/stats)
3636
[![Latest Stable Version](https://poser.pugx.org/enqueue/amqp-ext/version.png)](https://packagist.org/packages/enqueue/amqp-ext)
37-
* [AMQP](docs/transport/amqp_bunny.md) based on [bunny](https://github.com/jakubkulhan/bunny).
37+
* [AMQP](docs/transport/amqp_bunny.md) based on [bunny](https://github.com/jakubkulhan/bunny)
3838
[![Build Status](https://travis-ci.org/php-enqueue/amqp-bunny.png?branch=master)](https://travis-ci.org/php-enqueue/amqp-bunny)
3939
[![Total Downloads](https://poser.pugx.org/enqueue/amqp-bunny/d/total.png)](https://packagist.org/packages/enqueue/amqp-bunny/stats)
4040
[![Latest Stable Version](https://poser.pugx.org/enqueue/amqp-bunny/version.png)](https://packagist.org/packages/enqueue/amqp-bunny)
41-
* [AMQP(s)](docs/transport/amqp_lib.md) based on [php-amqplib](https://github.com/php-amqplib/php-amqplib).
41+
* [AMQP(s)](docs/transport/amqp_lib.md) based on [php-amqplib](https://github.com/php-amqplib/php-amqplib)
4242
[![Build Status](https://travis-ci.org/php-enqueue/amqp-lib.png?branch=master)](https://travis-ci.org/php-enqueue/amqp-lib)
4343
[![Total Downloads](https://poser.pugx.org/enqueue/amqp-lib/d/total.png)](https://packagist.org/packages/enqueue/amqp-lib/stats)
4444
[![Latest Stable Version](https://poser.pugx.org/enqueue/amqp-lib/version.png)](https://packagist.org/packages/enqueue/amqp-lib)
45-
* [Beanstalk](docs/transport/pheanstalk.md).
45+
* [Beanstalk](docs/transport/pheanstalk.md)
4646
[![Build Status](https://travis-ci.org/php-enqueue/pheanstalk.png?branch=master)](https://travis-ci.org/php-enqueue/pheanstalk)
4747
[![Total Downloads](https://poser.pugx.org/enqueue/pheanstalk/d/total.png)](https://packagist.org/packages/enqueue/pheanstalk/stats)
4848
[![Latest Stable Version](https://poser.pugx.org/enqueue/pheanstalk/version.png)](https://packagist.org/packages/enqueue/pheanstalk)
@@ -86,7 +86,7 @@ Features:
8686
[![Build Status](https://travis-ci.org/php-enqueue/wamp.png?branch=master)](https://travis-ci.org/php-enqueue/wamp)
8787
[![Total Downloads](https://poser.pugx.org/enqueue/wamp/d/total.png)](https://packagist.org/packages/enqueue/wamp/stats)
8888
[![Latest Stable Version](https://poser.pugx.org/enqueue/wamp/version.png)](https://packagist.org/packages/enqueue/wamp)
89-
* [Null](docs/transport/null.md).
89+
* [Null](docs/transport/null.md)
9090
[![Build Status](https://travis-ci.org/php-enqueue/null.png?branch=master)](https://travis-ci.org/php-enqueue/null)
9191
[![Total Downloads](https://poser.pugx.org/enqueue/null/d/total.png)](https://packagist.org/packages/enqueue/null/stats)
9292
[![Latest Stable Version](https://poser.pugx.org/enqueue/null/version.png)](https://packagist.org/packages/enqueue/null)

‎bin/subtree-split‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ remote async-command git@github.com:php-enqueue/async-command.git
6767
remote mongodb git@github.com:php-enqueue/mongodb.git
6868
remote dsn git@github.com:php-enqueue/dsn.git
6969
remote wamp git@github.com:php-enqueue/wamp.git
70+
remote monitoring git@github.com:php-enqueue/monitoring.git
7071

7172
split 'pkg/enqueue' enqueue
7273
split 'pkg/simple-client' simple-client
@@ -92,3 +93,4 @@ split 'pkg/async-command' async-command
9293
split 'pkg/mongodb' mongodb
9394
split 'pkg/dsn' dsn
9495
split 'pkg/wamp' wamp
96+
split 'pkg/monitoring' monitoring

‎docs/bundle/config_reference.md‎

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,49 @@ You can get this info by running `./bin/console config:dump-reference enqueue` c
1414
```yaml
1515
# Default configuration for extension with alias: "enqueue"
1616
enqueue:
17-
# Configuration name
18-
default:
17+
18+
# Prototype
19+
key:
20+
1921
# The transport option could accept a string DSN, an array with DSN key, or null. It accept extra options. To find out what option you can set, look at connection factory constructor docblock.
20-
transport:
21-
22-
# The broker DSN. These schemes are supported: "file", "amqp", "amqps", "db2", "ibm-db2", "mssql", "sqlsrv", "mysql", "mysql2", "pgsql", "postgres", "sqlite", "sqlite3", "null", "gearman", "beanstalk", "kafka", "rdkafka", "redis", "stomp", "sqs", "gps", "mongodb", to use these "file", "amqp", "amqps", "db2", "ibm-db2", "mssql", "sqlsrv", "mysql", "mysql2", "pgsql", "postgres", "sqlite", "sqlite3", "null", "gearman", "beanstalk", "kafka", "rdkafka", "redis", "stomp", "sqs", "gps", "mongodb" you have to install a package.
22+
transport: # Required
23+
24+
# The MQ broker DSN. These schemes are supported: "file", "amqp", "amqps", "db2", "ibm-db2", "mssql", "sqlsrv", "mysql", "mysql2", "pgsql", "postgres", "sqlite", "sqlite3", "null", "gearman", "beanstalk", "kafka", "rdkafka", "redis", "stomp", "sqs", "gps", "mongodb", "wamp", "ws", to use these "file", "amqp", "amqps", "db2", "ibm-db2", "mssql", "sqlsrv", "mysql", "mysql2", "pgsql", "postgres", "sqlite", "sqlite3", "null", "gearman", "beanstalk", "kafka", "rdkafka", "redis", "stomp", "sqs", "gps", "mongodb", "wamp", "ws" you have to install a package.
2325
dsn: ~ # Required
24-
26+
2527
# The connection factory class should implement "Interop\Queue\ConnectionFactory" interface
2628
connection_factory_class: ~
27-
29+
2830
# The factory class should implement "Enqueue\ConnectionFactoryFactoryInterface" interface
2931
factory_service: ~
30-
32+
3133
# The factory service should be a class that implements "Enqueue\ConnectionFactoryFactoryInterface" interface
3234
factory_class: ~
35+
consumption:
36+
37+
# the time in milliseconds queue consumer waits for a message (100 ms by default)
38+
receive_timeout: 10000
3339
client:
3440
traceable_producer: true
3541
prefix: enqueue
3642
app_name: app
3743
router_topic: default
3844
router_queue: default
39-
router_processor: Enqueue\Client\RouterProcessor
45+
router_processor: null
4046
default_processor_queue: default
4147
redelivered_delay_time: 0
42-
consumption:
43-
44-
# the time in milliseconds queue consumer waits for a message (100 ms by default)
45-
receive_timeout: 100
48+
49+
# The "monitoring" option could accept a string DSN, an array with DSN key, or null. It accept extra options. To find out what option you can set, look at stats storage constructor doc block.
50+
monitoring:
51+
52+
# The stats storage DSN. These schemes are supported: "wamp", "ws", "influxdb".
53+
dsn: ~ # Required
54+
55+
# The factory class should implement "Enqueue\Monitoring\StatsStorageFactory" interface
56+
storage_factory_service: ~
57+
58+
# The factory service should be a class that implements "Enqueue\Monitoring\StatsStorageFactory" interface
59+
storage_factory_class: ~
4660
job: false
4761
async_commands:
4862
enabled: false

‎docs/client/message_examples.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,13 @@ $producer->sendEvent('aTopic', $message);
7272
## Priority
7373

7474
You can set a priority If you want a message to be processed quicker than other messages in the queue.
75-
Client defines five priority constants: `MessagePriority::VERY_LOW`, `MessagePriority::LOW`, `MessagePriority::NORMAL`, `MessagePriority::HIGH`, `MessagePriority::VERY_HIGH`.
76-
The `MessagePriority::NORMAL` is default priority.
75+
Client defines five priority constants:
76+
77+
* `MessagePriority::VERY_LOW`
78+
* `MessagePriority::LOW`
79+
* `MessagePriority::NORMAL` (**default**)
80+
* `MessagePriority::HIGH`
81+
* `MessagePriority::VERY_HIGH`
7782

7883
```php
7984
<?php

‎docs/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,4 @@ Enqueue is an MIT-licensed open source project with its ongoing development made
8989
* [Spool Swiftmailer emails to real message queue.](https://blog.forma-pro.com/spool-swiftmailer-emails-to-real-message-queue-9ecb8b53b5de)
9090
* [Yii PHP Framework has adopted AMQP Interop.](https://blog.forma-pro.com/yii-php-framework-has-adopted-amqp-interop-85ab47c9869f)
9191
* [(En)queue Symfony console commands](http://tech.yappa.be/enqueue-symfony-console-commands)
92+
* [From RabbitMq to PhpEnqueue via Symfony Messenger](https://medium.com/@stefanoalletti_40357/from-rabbitmq-to-phpenqueue-via-symfony-messenger-b8260d0e506c)

‎pkg/dbal/DbalConsumer.php‎

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Enqueue\Dbal;
66

77
use Doctrine\DBAL\Connection;
8-
use Doctrine\DBAL\Types\Type;
98
use Interop\Queue\Consumer;
109
use Interop\Queue\Exception\InvalidMessageException;
1110
use Interop\Queue\Impl\ConsumerPollingTrait;
@@ -81,14 +80,7 @@ public function receiveNoWait(): ?Message
8180
$this->removeExpiredMessages();
8281
$this->redeliverMessages();
8382

84-
// get top message from the queue
85-
if ($message = $this->fetchMessage([$this->queue->getQueueName()], $redeliveryDelay)) {
86-
if ($message['redelivered'] || empty($message['time_to_live']) || $message['time_to_live'] > time()) {
87-
return $this->getContext()->convertMessage($message);
88-
}
89-
}
90-
91-
return null;
83+
return $this->fetchMessage([$this->queue->getQueueName()], $redeliveryDelay);
9284
}
9385

9486
/**
@@ -129,17 +121,4 @@ protected function getConnection(): Connection
129121
{
130122
return $this->dbal;
131123
}
132-
133-
private function deleteMessage(string $deliveryId): void
134-
{
135-
if (empty($deliveryId)) {
136-
throw new \LogicException(sprintf('Expected record was removed but it is not. Delivery id: "%s"', $deliveryId));
137-
}
138-
139-
$this->getConnection()->delete(
140-
$this->getContext()->getTableName(),
141-
['delivery_id' => $deliveryId],
142-
['delivery_id' => Type::STRING]
143-
);
144-
}
145124
}

‎pkg/dbal/DbalConsumerHelperTrait.php‎

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,96 +11,137 @@
1111

1212
trait DbalConsumerHelperTrait
1313
{
14+
private $redeliverMessagesLastExecutedAt;
15+
16+
private $removeExpiredMessagesLastExecutedAt;
17+
1418
abstract protected function getContext(): DbalContext;
1519

1620
abstract protected function getConnection(): Connection;
1721

18-
protected function fetchMessage(array $queues, int $redeliveryDelay): ?array
22+
protected function fetchMessage(array $queues, int $redeliveryDelay): ?DbalMessage
1923
{
24+
if (empty($queues)) {
25+
throw new \LogicException('Queues must not be empty.');
26+
}
27+
2028
$now = time();
21-
$deliveryId = (string) Uuid::uuid1();
22-
23-
$this->getConnection()->beginTransaction();
24-
25-
try {
26-
$query = $this->getConnection()->createQueryBuilder()
27-
->select('*')
28-
->from($this->getContext()->getTableName())
29-
->andWhere('delivery_id IS NULL')
30-
->andWhere('delayed_until IS NULL OR delayed_until <= :delayedUntil')
31-
->andWhere('queue IN (:queues)')
32-
->addOrderBy('priority', 'desc')
33-
->addOrderBy('published_at', 'asc')
34-
->setMaxResults(1);
35-
36-
// select for update
37-
$message = $this->getConnection()->executeQuery(
38-
$query->getSQL().' '.$this->getConnection()->getDatabasePlatform()->getWriteLockSQL(),
39-
['delayedUntil' => $now, 'queues' => array_values($queues)],
40-
['delayedUntil' => ParameterType::INTEGER, 'queues' => Connection::PARAM_STR_ARRAY]
41-
)->fetch();
42-
43-
if (!$message) {
44-
$this->getConnection()->commit();
29+
$deliveryId = Uuid::uuid4();
30+
31+
$endAt = microtime(true) + 0.2; // add 200ms
32+
33+
$select = $this->getConnection()->createQueryBuilder()
34+
->select('id')
35+
->from($this->getContext()->getTableName())
36+
->andWhere('queue IN (:queues)')
37+
->andWhere('delayed_until IS NULL OR delayed_until <= :delayedUntil')
38+
->andWhere('delivery_id IS NULL')
39+
->addOrderBy('priority', 'asc')
40+
->addOrderBy('published_at', 'asc')
41+
->setParameter('queues', $queues, Connection::PARAM_STR_ARRAY)
42+
->setParameter('delayedUntil', $now, ParameterType::INTEGER)
43+
->setMaxResults(1);
44+
45+
$update = $this->getConnection()->createQueryBuilder()
46+
->update($this->getContext()->getTableName())
47+
->set('delivery_id', ':deliveryId')
48+
->set('redeliver_after', ':redeliverAfter')
49+
->andWhere('id = :messageId')
50+
->andWhere('delivery_id IS NULL')
51+
->setParameter('deliveryId', $deliveryId->getBytes(), Type::GUID)
52+
->setParameter('redeliverAfter', $now + $redeliveryDelay, Type::BIGINT)
53+
;
4554

55+
while (microtime(true) < $endAt) {
56+
$result = $select->execute()->fetch();
57+
if (empty($result)) {
4658
return null;
4759
}
4860

49-
// mark message as delivered to consumer
50-
$this->getConnection()->createQueryBuilder()
51-
->andWhere('id = :id')
52-
->update($this->getContext()->getTableName())
53-
->set('delivery_id', ':deliveryId')
54-
->set('redeliver_after', ':redeliverAfter')
55-
->setParameter('id', $message['id'], Type::GUID)
56-
->setParameter('deliveryId', $deliveryId, Type::STRING)
57-
->setParameter('redeliverAfter', $now + $redeliveryDelay, Type::BIGINT)
58-
->execute()
61+
$update
62+
->setParameter('messageId', $result['id'], Type::GUID)
5963
;
6064

61-
$this->getConnection()->commit();
62-
63-
$deliveredMessage = $this->getConnection()->createQueryBuilder()
64-
->select('*')
65-
->from($this->getContext()->getTableName())
66-
->andWhere('delivery_id = :deliveryId')
67-
->setParameter('deliveryId', $deliveryId, Type::STRING)
68-
->setMaxResults(1)
69-
->execute()
70-
->fetch()
71-
;
72-
73-
return $deliveredMessage ?: null;
74-
} catch (\Exception $e) {
75-
$this->getConnection()->rollBack();
76-
77-
throw $e;
65+
if ($update->execute()) {
66+
$deliveredMessage = $this->getConnection()->createQueryBuilder()
67+
->select('*')
68+
->from($this->getContext()->getTableName())
69+
->andWhere('delivery_id = :deliveryId')
70+
->setParameter('deliveryId', $deliveryId->getBytes(), Type::GUID)
71+
->setMaxResults(1)
72+
->execute()
73+
->fetch()
74+
;
75+
76+
if (false == $deliveredMessage) {
77+
throw new \LogicException('There must be a message at all times at this stage but there is no a message.');
78+
}
79+
80+
if ($deliveredMessage['redelivered'] || empty($deliveredMessage['time_to_live']) || $deliveredMessage['time_to_live'] > time()) {
81+
return $this->getContext()->convertMessage($deliveredMessage);
82+
}
83+
}
7884
}
85+
86+
return null;
7987
}
8088

8189
protected function redeliverMessages(): void
8290
{
83-
$this->getConnection()->createQueryBuilder()
91+
if (null === $this->redeliverMessagesLastExecutedAt) {
92+
$this->redeliverMessagesLastExecutedAt = microtime(true);
93+
} elseif ((microtime(true) - $this->redeliverMessagesLastExecutedAt) < 1) {
94+
return;
95+
}
96+
97+
$update = $this->getConnection()->createQueryBuilder()
8498
->update($this->getContext()->getTableName())
8599
->set('delivery_id', ':deliveryId')
86100
->set('redelivered', ':redelivered')
87-
->andWhere('delivery_id IS NOT NULL')
88101
->andWhere('redeliver_after < :now')
89-
->setParameter(':now', (int) time(), Type::BIGINT)
90-
->setParameter('deliveryId', null, Type::STRING)
102+
->andWhere('delivery_id IS NOT NULL')
103+
->setParameter(':now', time(), Type::BIGINT)
104+
->setParameter('deliveryId', null, Type::GUID)
91105
->setParameter('redelivered', true, Type::BOOLEAN)
92-
->execute()
93106
;
107+
108+
$update->execute();
109+
110+
$this->redeliverMessagesLastExecutedAt = microtime(true);
94111
}
95112

96113
protected function removeExpiredMessages(): void
97114
{
98-
$this->getConnection()->createQueryBuilder()
115+
if (null === $this->removeExpiredMessagesLastExecutedAt) {
116+
$this->removeExpiredMessagesLastExecutedAt = microtime(true);
117+
} elseif ((microtime(true) - $this->removeExpiredMessagesLastExecutedAt) < 1) {
118+
return;
119+
}
120+
121+
$delete = $this->getConnection()->createQueryBuilder()
99122
->delete($this->getContext()->getTableName())
100123
->andWhere('(time_to_live IS NOT NULL) AND (time_to_live < :now)')
101-
->setParameter(':now', (int) time(), Type::BIGINT)
102-
->setParameter('redelivered', false, Type::BOOLEAN)
103-
->execute()
124+
->andWhere('delivery_id IS NULL')
125+
->andWhere('redelivered = false')
126+
127+
->setParameter(':now', time(), Type::BIGINT)
104128
;
129+
130+
$delete->execute();
131+
132+
$this->removeExpiredMessagesLastExecutedAt = microtime(true);
133+
}
134+
135+
private function deleteMessage(string $deliveryId): void
136+
{
137+
if (empty($deliveryId)) {
138+
throw new \LogicException(sprintf('Expected record was removed but it is not. Delivery id: "%s"', $deliveryId));
139+
}
140+
141+
$this->getConnection()->delete(
142+
$this->getContext()->getTableName(),
143+
['delivery_id' => Uuid::fromString($deliveryId)->getBytes()],
144+
['delivery_id' => Type::GUID]
145+
);
105146
}
106147
}

0 commit comments

Comments
 (0)