Skip to content

Commit b4a158b

Browse files
jacobarriolakidunot89
authored andcommitted
Add timestamp to gracefully remove stale transactions
There are instances where a stale transaction stays around do to an error in the mutation. As a result, the recursive update_transaction_queue method will continue to run over and over, causing an inifinte loop and potentially crashing the server. This attempts to add a timestamp once the transaction is validated when checking via the timestamp and will remove it from the queue.
1 parent 3bdcb09 commit b4a158b

File tree

1 file changed

+69
-5
lines changed

1 file changed

+69
-5
lines changed

includes/utils/class-session-transaction-manager.php

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,15 @@ public function update_transaction_queue( $source, $args, $context, $info ) {
124124
return;
125125
}
126126

127-
// Initialize transaction ID.
128-
if ( is_null( $this->transaction_id ) ) {
127+
128+
// Bail if transaction has already been completed. There are times when the underlying action runs twice.
129+
if ( ! is_null( $this->transaction_id ) ) {
130+
$transaction_queue = get_transient( "woo_session_transactions_queue_{$this->session_handler->get_customer_id()}" );
131+
if ( in_array( $this->transaction_id, array_column( $transaction_queue, 'transaction_id' ), true ) ) {
132+
return;
133+
}
134+
} else {
135+
// Initialize transaction ID.
129136
$mutation = $info->fieldName; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
130137
$this->transaction_id = \uniqid( "wooSession_{$mutation}_" );
131138
}
@@ -136,6 +143,9 @@ public function update_transaction_queue( $source, $args, $context, $info ) {
136143
$this->update_transaction_queue( $source, $args, $context, $info );
137144
} else {
138145
$this->session_handler->reload_data();
146+
147+
// Set a timestamp on the transaction, which will allow us to check for any stale transactions that accidentally get left behind.
148+
$this->set_timestamp();
139149
}
140150
}
141151

@@ -156,6 +166,10 @@ public function next_transaction() {
156166
// If current transaction is the lead exit loop.
157167
} elseif ( $this->transaction_id === $transaction_queue[0]['transaction_id'] ) {
158168
return true;
169+
} elseif ( true === $this->did_transaction_expire( $transaction_queue ) ) {
170+
// If transaction has expired, remove it from the queue array and continue loop
171+
array_shift( $transaction_queue );
172+
$this->save_transaction_queue( $transaction_queue );
159173
}
160174

161175
return false;
@@ -175,9 +189,10 @@ public function get_transaction_queue() {
175189
}
176190

177191
// If transaction ID not in queue, add it, and start transaction.
178-
if ( false === array_search( $this->transaction_id, array_column( $transaction_queue, 'transaction_id' ), true ) ) {
179-
$transaction_id = $this->transaction_id;
180-
$snapshot = $this->session_handler->get_session_data();
192+
if ( ! in_array( $this->transaction_id, array_column( $transaction_queue, 'transaction_id' ), true ) ) {
193+
$transaction_id = $this->transaction_id;
194+
$snapshot = $this->session_handler->get_session_data();
195+
181196
$transaction_queue[] = compact( 'transaction_id', 'snapshot' );
182197

183198
// Update queue.
@@ -228,4 +243,53 @@ public function save_transaction_queue( $queue = array() ) {
228243
// Save transaction queue.
229244
set_transient( "woo_session_transactions_queue_{$this->session_handler->get_customer_id()}", $queue );
230245
}
246+
247+
public function set_timestamp() {
248+
$transaction_queue = $this->get_transaction_queue();
249+
250+
// Bail if we don't have a queue to add a timestamp against.
251+
if ( empty( $transaction_queue[0] ) ) {
252+
return;
253+
}
254+
255+
$transaction_queue[0]['timestamp'] = time();
256+
257+
$this->save_transaction_queue( $transaction_queue );
258+
}
259+
260+
/**
261+
* The length of time in seconds a transaction should stay in the queue
262+
*
263+
* @return mixed|void
264+
*/
265+
public function get_timestamp_threshold() {
266+
return apply_filters( 'woographql_session_transaction_timeout', 30 );
267+
}
268+
269+
/**
270+
* Whether the transaction has expired. This helps prevent infinite loops while searching through the transaction
271+
* queue.
272+
*
273+
* @param $transaction_queue
274+
*
275+
* @return bool
276+
*/
277+
public function did_transaction_expire( $transaction_queue ) {
278+
// Guard against empty transaction queue. We assume that it is invalid since we cannot calculate.
279+
if ( empty( $transaction_queue ) ) {
280+
return true;
281+
}
282+
283+
// Guard against empty timestamp. We assume that it is invalid since we cannot calculate.
284+
if ( empty( $transaction_queue[0] ) || empty( $transaction_queue[0]['timestamp'] ) ) {
285+
return true;
286+
}
287+
288+
$now = time();
289+
$stamp = $transaction_queue[0]['timestamp'];
290+
$threshold = $this->get_timestamp_threshold();
291+
$difference = $now - $stamp;
292+
293+
return $difference > $threshold;
294+
}
231295
}

0 commit comments

Comments
 (0)