1111
1212trait DbalConsumerHelperTrait
1313{
14+ private $ redeliverMessagesLastExecutedAt ;
15+
16+ private $ removeExpiredMessagesLastExecutedAt ;
17+
1418 abstract protected function getContext (): DbalContext ;
1519
1620 abstract protected function getConnection (): Connection ;
@@ -20,66 +24,72 @@ protected function fetchMessage(array $queues, int $redeliveryDelay): ?array
2024 $ now = time ();
2125 $ deliveryId = (string ) Uuid ::uuid1 ();
2226
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 ();
27+ $ endAt = microtime (true ) + 0.2 ; // add 200ms
28+
29+ $ select = $ this ->getConnection ()->createQueryBuilder ()
30+ ->select ('id ' )
31+ ->from ($ this ->getContext ()->getTableName ())
32+ ->andWhere ('delivery_id IS NULL ' )
33+ ->andWhere ('delayed_until IS NULL OR delayed_until <= :delayedUntil ' )
34+ ->andWhere ('queue IN (:queues) ' )
35+ ->addOrderBy ('priority ' , 'desc ' )
36+ ->addOrderBy ('published_at ' , 'asc ' )
37+ ->setParameter ('delayedUntil ' , $ now , ParameterType::INTEGER )
38+ ->setParameter ('queues ' , array_values ($ queues ), Connection::PARAM_STR_ARRAY )
39+ ->setMaxResults (1 );
40+
41+ $ update = $ this ->getConnection ()->createQueryBuilder ()
42+ ->update ($ this ->getContext ()->getTableName ())
43+ ->set ('delivery_id ' , ':deliveryId ' )
44+ ->set ('redeliver_after ' , ':redeliverAfter ' )
45+ ->andWhere ('id = :messageId ' )
46+ ->andWhere ('delivery_id IS NULL ' )
47+ ->setParameter ('deliveryId ' , $ deliveryId , Type::STRING )
48+ ->setParameter ('redeliverAfter ' , $ now + $ redeliveryDelay , Type::BIGINT )
49+ ;
4550
51+ while (microtime () < $ endAt ) {
52+ $ result = $ select ->execute ()->fetch ();
53+ if (empty ($ result )) {
4654 return null ;
4755 }
4856
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 ()
57+ $ update
58+ ->setParameter ('messageId ' , $ result ['id ' ], Type::GUID )
5959 ;
6060
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 ;
61+ if ( $ update -> execute ()) {
62+ $ deliveredMessage = $ this -> getConnection ()-> createQueryBuilder ()
63+ -> select ( ' * ' )
64+ -> from ( $ this -> getContext ()-> getTableName () )
65+ -> andWhere ( ' delivery_id = :deliveryId ' )
66+ -> setParameter ( ' deliveryId ' , $ deliveryId , Type:: STRING )
67+ -> setMaxResults ( 1 )
68+ -> execute ( )
69+ -> fetch ()
70+ ;
71+
72+ if ( false == $ deliveredMessage ) {
73+ throw new \ LogicException ( ' There must be a message at all times at this stage but there is no a message. ' ) ;
74+ }
75+
76+ return $ deliveredMessage ;
77+ }
7878 }
79+
80+ return null ;
7981 }
8082
8183 protected function redeliverMessages (): void
8284 {
85+ if (null === $ this ->redeliverMessagesLastExecutedAt ) {
86+ $ this ->redeliverMessagesLastExecutedAt = microtime (true );
87+ }
88+
89+ if ((microtime (true ) - $ this ->redeliverMessagesLastExecutedAt ) < 1 ) {
90+ return ;
91+ }
92+
8393 $ this ->getConnection ()->createQueryBuilder ()
8494 ->update ($ this ->getContext ()->getTableName ())
8595 ->set ('delivery_id ' , ':deliveryId ' )
@@ -91,16 +101,28 @@ protected function redeliverMessages(): void
91101 ->setParameter ('redelivered ' , true , Type::BOOLEAN )
92102 ->execute ()
93103 ;
104+
105+ $ this ->redeliverMessagesLastExecutedAt = microtime (true );
94106 }
95107
96108 protected function removeExpiredMessages (): void
97109 {
110+ if (null === $ this ->removeExpiredMessagesLastExecutedAt ) {
111+ $ this ->removeExpiredMessagesLastExecutedAt = microtime (true );
112+ }
113+
114+ if ((microtime (true ) - $ this ->removeExpiredMessagesLastExecutedAt ) < 1 ) {
115+ return ;
116+ }
117+
98118 $ this ->getConnection ()->createQueryBuilder ()
99119 ->delete ($ this ->getContext ()->getTableName ())
100120 ->andWhere ('(time_to_live IS NOT NULL) AND (time_to_live < :now) ' )
101121 ->setParameter (':now ' , (int ) time (), Type::BIGINT )
102122 ->setParameter ('redelivered ' , false , Type::BOOLEAN )
103123 ->execute ()
104124 ;
125+
126+ $ this ->removeExpiredMessagesLastExecutedAt = microtime (true );
105127 }
106128}
0 commit comments