Skip to content

Commit

Permalink
🚧 chore: setup support client side rate limiting
Browse files Browse the repository at this point in the history
  • Loading branch information
gowrizrh committed Feb 13, 2024
1 parent 66f149e commit e1dc3d0
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 38 deletions.
6 changes: 1 addition & 5 deletions Api/Data/ResultInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ public function getIsRetryable(): bool;

public function setIsRetryable(bool $isRetryable): void;

public function getRetryAfter(): int;
public function getRetryAfter(): ?int;

public function setRetryAfter(int $retryAfter): void;

public function isSuccessful(): bool;

public function isRetryable(): bool;
}
2 changes: 1 addition & 1 deletion Api/RetryManagementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface RetryManagementInterface
{
public function init(int $subscriptionId, mixed $data, string $uuid): void;

public function place(int $deathCount, int $subscriptionId, mixed $data, string $uuid, int $backoff = 0): void;
public function place(int $deathCount, int $subscriptionId, mixed $data, string $uuid, ?int $backoff): void;

public function kill(int $subscriptionId, mixed $data): void;
}
35 changes: 34 additions & 1 deletion Helper/NotifierResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
namespace MageOS\AsyncEvents\Helper;

use Magento\Framework\DataObject;
use MageOS\AsyncEvents\Api\Data\ResultInterface;

class NotifierResult extends DataObject
class NotifierResult extends DataObject implements ResultInterface
{
private const SUCCESS = 'success';
private const SUBSCRIPTION_ID = 'subscription_id';
private const RESPONSE_DATA = 'response_data';
private const UUID = 'uuid';
private const DATA = 'data';
private const IS_RETRYABLE = 'is_retryable';
private const RETRY_AFTER = 'retry_after';

/**
* Getter for success
Expand Down Expand Up @@ -118,4 +121,34 @@ public function setAsyncEventData(array $eventData): void
{
$this->setData(self::DATA, $eventData);
}

public function getIsSuccessful(): bool
{
return $this->getSuccess();
}

public function setIsSuccessful(bool $isSuccessful): void
{
$this->setSuccess($isSuccessful);
}

public function getIsRetryable(): bool
{
return (bool) $this->getData(self::IS_RETRYABLE);
}

public function setIsRetryable(bool $isRetryable): void
{
$this->setData(self::IS_RETRYABLE, $isRetryable);
}

public function getRetryAfter(): ?int
{
return $this->getData(self::RETRY_AFTER);
}

public function setRetryAfter(int $retryAfter): void
{
$this->setData(self::RETRY_AFTER, $retryAfter);
}
}
16 changes: 11 additions & 5 deletions Model/RetryHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,21 @@ public function process(array $message): void
foreach ($asyncEvents as $asyncEvent) {
$handler = $asyncEvent->getMetadata();
$notifier = $this->notifierFactory->create($handler);
$response = $notifier->notify($asyncEvent, [
$result = $notifier->notify($asyncEvent, [
'data' => $data
]);
$response->setUuid($uuid);
$this->log($response);
$result->setUuid($uuid);
$this->log($result);

if (!$response->getSuccess()) {
if (!$result->getIsSuccessful() && $result->getIsRetryable()) {
if ($deathCount < $maxDeaths) {
$this->retryManager->place($deathCount + 1, $subscriptionId, $data, $uuid);
$this->retryManager->place(
++$deathCount,
$subscriptionId,
$data,
$uuid,
$result->getRetryAfter()
);
} else {
$this->retryManager->kill($subscriptionId, $data);
}
Expand Down
8 changes: 4 additions & 4 deletions Service/AsyncEvent/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ public function dispatch(string $eventName, mixed $output, int $storeId = 0): vo

$notifier = $this->notifierFactory->create($handler);

$response = $notifier->notify(
$result = $notifier->notify(
$asyncEvent,
[
'data' => $output
]
);

$uuid = $this->identityService->generateId();
$response->setUuid($uuid);
$result->setUuid($uuid);

$this->log($response);
$this->log($result);

if (!$response->getSuccess()) {
if (!$result->getIsSuccessful() && $result->getIsRetryable()) {
$this->retryManager->init($asyncEvent->getSubscriptionId(), $output, $uuid);
}
}
Expand Down
16 changes: 10 additions & 6 deletions Service/AsyncEvent/HttpNotifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,30 @@ public function notify(AsyncEventInterface $asyncEvent, array $data): NotifierRe
]
);

$notifierResult->setSuccess(
$response->getStatusCode() >= 200
&& $response->getStatusCode() < 300
);

$notifierResult->setIsSuccessful(true);
$notifierResult->setResponseData($response->getBody()->getContents());

} catch (RequestException $exception) {
/**
* Catch a RequestException, so we cover even the network layer exceptions which might sometimes
* not have a response.
*/
$notifierResult->setSuccess(false);
$notifierResult->setIsSuccessful(false);

if ($exception->hasResponse()) {
$response = $exception->getResponse();
$responseContent = $response->getBody()->getContents();
$exceptionMessage = !empty($responseContent) ? $responseContent : $response->getReasonPhrase();

$notifierResult->setResponseData($exceptionMessage);
$notifierResult->setIsRetryable(true);

// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
if ($response->hasHeader('Retry-After')) {
$retryAfter = $response->getHeader('Retry-After')[0];
$notifierResult->setRetryAfter((int) $retryAfter);
}

} else {
$notifierResult->setResponseData(
$exception->getMessage()
Expand Down
6 changes: 3 additions & 3 deletions Service/AsyncEvent/NotifierInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace MageOS\AsyncEvents\Service\AsyncEvent;

use MageOS\AsyncEvents\Api\Data\AsyncEventInterface;
use MageOS\AsyncEvents\Helper\NotifierResult;
use MageOS\AsyncEvents\Api\Data\ResultInterface;

interface NotifierInterface
{
Expand All @@ -14,7 +14,7 @@ interface NotifierInterface
*
* @param AsyncEventInterface $asyncEvent
* @param array $data
* @return NotifierResult
* @return ResultInterface
*/
public function notify(AsyncEventInterface $asyncEvent, array $data): NotifierResult;
public function notify(AsyncEventInterface $asyncEvent, array $data): ResultInterface;
}
12 changes: 9 additions & 3 deletions Service/AsyncEvent/RetryManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace MageOS\AsyncEvents\Service\AsyncEvent;

use MageOS\AsyncEvents\Api\RetryManagementInterface;
use MageOS\AsyncEvents\Helper\QueueMetadataInterface;
use Magento\Framework\Amqp\ConfigPool;
use Magento\Framework\Amqp\Topology\BindingInstallerInterface;
Expand All @@ -12,7 +13,7 @@
use Magento\Framework\MessageQueue\Topology\Config\QueueConfigItemFactory;
use Magento\Framework\Serialize\SerializerInterface;

class RetryManager
class RetryManager implements RetryManagementInterface
{
public const DEATH_COUNT = 'death_count';
public const SUBSCRIPTION_ID = 'subscription_id';
Expand Down Expand Up @@ -70,11 +71,15 @@ public function init(int $subscriptionId, mixed $data, string $uuid): void
* @param int $subscriptionId
* @param mixed $data
* @param string $uuid
* @param int|null $backoff
* @return void
*/
public function place(int $deathCount, int $subscriptionId, mixed $data, string $uuid): void
public function place(int $deathCount, int $subscriptionId, mixed $data, string $uuid, ?int $backoff): void
{
$backoff = $this->calculateBackoff($deathCount);
if (!$backoff) {
$backoff = $this->calculateBackoff($deathCount);
}

$queueName = 'event.delay.' . $backoff;
$retryRoutingKey = 'event.retry.' . $backoff;

Expand Down Expand Up @@ -120,6 +125,7 @@ public function kill(int $subscriptionId, mixed $data): void
private function assertDelayQueue(int $backoff, string $queueName, string $retryRoutingKey): void
{
$config = $this->configPool->get('amqp');
$backoff = abs($backoff);

$queueConfigItem = $this->queueConfigItemFactory->create();
$queueConfigItem->setData([
Expand Down
19 changes: 9 additions & 10 deletions Test/Integration/FailoverTopologyTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?php


declare(strict_types=1);

namespace MageOS\AsyncEvents\Test\Integration;
Expand Down Expand Up @@ -51,15 +50,15 @@ public function testDelayQueueCreation(): void
/**
* Place events at different death levels
*/
$this->retryManager->place(2, 1, 'test', 'uuid');
$this->retryManager->place(3, 1, 'test', 'uuid');
$this->retryManager->place(4, 1, 'test', 'uuid');
$this->retryManager->place(5, 1, 'test', 'uuid');
$this->retryManager->place(6, 1, 'test', 'uuid');
$this->retryManager->place(7, 1, 'test', 'uuid');
$this->retryManager->place(8, 1, 'test', 'uuid');
$this->retryManager->place(9, 1, 'test', 'uuid');
$this->retryManager->place(10, 1, 'test', 'uuid');
$this->retryManager->place(2, 1, 'test', 'uuid', null);
$this->retryManager->place(3, 1, 'test', 'uuid', null);
$this->retryManager->place(4, 1, 'test', 'uuid', null);
$this->retryManager->place(5, 1, 'test', 'uuid', null);
$this->retryManager->place(6, 1, 'test', 'uuid', null);
$this->retryManager->place(7, 1, 'test', 'uuid', null);
$this->retryManager->place(8, 1, 'test', 'uuid', null);
$this->retryManager->place(9, 1, 'test', 'uuid', null);
$this->retryManager->place(10, 1, 'test', 'uuid', null);

$bindings = $this->helper->getExchangeBindings('event.failover');

Expand Down

0 comments on commit e1dc3d0

Please sign in to comment.