Reduce idle payment confirm polling
This commit is contained in:
@@ -104,6 +104,9 @@ POLYWEATHER_PAYMENT_POINTS_MAX_DISCOUNT_USDC=3
|
|||||||
# 支付自动补单
|
# 支付自动补单
|
||||||
POLYWEATHER_PAYMENT_EVENT_LOOP_ENABLED=true
|
POLYWEATHER_PAYMENT_EVENT_LOOP_ENABLED=true
|
||||||
POLYWEATHER_PAYMENT_CONFIRM_LOOP_ENABLED=true
|
POLYWEATHER_PAYMENT_CONFIRM_LOOP_ENABLED=true
|
||||||
|
POLYWEATHER_PAYMENT_CONFIRM_LOOP_INTERVAL_SEC=20
|
||||||
|
POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_INTERVAL_SEC=300
|
||||||
|
POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_AFTER_EMPTY_CYCLES=3
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. 验证步骤
|
## 5. 验证步骤
|
||||||
|
|||||||
@@ -229,9 +229,19 @@ class StartupCoordinator:
|
|||||||
|
|
||||||
def _start_payment_confirm_loop(self) -> LoopStatus:
|
def _start_payment_confirm_loop(self) -> LoopStatus:
|
||||||
enabled = _env_bool("POLYWEATHER_PAYMENT_CONFIRM_LOOP_ENABLED", True)
|
enabled = _env_bool("POLYWEATHER_PAYMENT_CONFIRM_LOOP_ENABLED", True)
|
||||||
|
interval_sec = max(
|
||||||
|
5, _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_INTERVAL_SEC", 20)
|
||||||
|
)
|
||||||
|
idle_interval_sec = max(
|
||||||
|
interval_sec,
|
||||||
|
_env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_INTERVAL_SEC", 300),
|
||||||
|
)
|
||||||
details = {
|
details = {
|
||||||
"interval_sec": max(
|
"interval_sec": interval_sec,
|
||||||
5, _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_INTERVAL_SEC", 20)
|
"idle_interval_sec": idle_interval_sec,
|
||||||
|
"idle_after_empty_cycles": max(
|
||||||
|
1,
|
||||||
|
_env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_AFTER_EMPTY_CYCLES", 3),
|
||||||
),
|
),
|
||||||
"batch_size": max(
|
"batch_size": max(
|
||||||
1, min(200, _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_BATCH_SIZE", 20))
|
1, min(200, _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_BATCH_SIZE", 20))
|
||||||
|
|||||||
@@ -67,12 +67,28 @@ def _runner() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
interval_sec = _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_INTERVAL_SEC", 20, 5)
|
interval_sec = _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_INTERVAL_SEC", 20, 5)
|
||||||
|
idle_interval_sec = _env_int(
|
||||||
|
"POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_INTERVAL_SEC",
|
||||||
|
300,
|
||||||
|
interval_sec,
|
||||||
|
)
|
||||||
|
idle_interval_sec = max(interval_sec, idle_interval_sec)
|
||||||
|
idle_after_empty_cycles = _env_int(
|
||||||
|
"POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_AFTER_EMPTY_CYCLES",
|
||||||
|
3,
|
||||||
|
1,
|
||||||
|
)
|
||||||
batch_size = _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_BATCH_SIZE", 20, 1)
|
batch_size = _env_int("POLYWEATHER_PAYMENT_CONFIRM_LOOP_BATCH_SIZE", 20, 1)
|
||||||
batch_size = min(batch_size, 200)
|
batch_size = min(batch_size, 200)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"payment confirm loop started interval={}s batch={} chain_id={} confirmations={}",
|
(
|
||||||
|
"payment confirm loop started active_interval={}s idle_interval={}s "
|
||||||
|
"idle_after_empty_cycles={} batch={} chain_id={} confirmations={}"
|
||||||
|
),
|
||||||
interval_sec,
|
interval_sec,
|
||||||
|
idle_interval_sec,
|
||||||
|
idle_after_empty_cycles,
|
||||||
batch_size,
|
batch_size,
|
||||||
PAYMENT_CHECKOUT.chain_id,
|
PAYMENT_CHECKOUT.chain_id,
|
||||||
PAYMENT_CHECKOUT.confirmations,
|
PAYMENT_CHECKOUT.confirmations,
|
||||||
@@ -81,13 +97,17 @@ def _runner() -> None:
|
|||||||
"confirm_loop_started",
|
"confirm_loop_started",
|
||||||
{
|
{
|
||||||
"interval_sec": interval_sec,
|
"interval_sec": interval_sec,
|
||||||
|
"idle_interval_sec": idle_interval_sec,
|
||||||
|
"idle_after_empty_cycles": idle_after_empty_cycles,
|
||||||
"batch_size": batch_size,
|
"batch_size": batch_size,
|
||||||
"chain_id": PAYMENT_CHECKOUT.chain_id,
|
"chain_id": PAYMENT_CHECKOUT.chain_id,
|
||||||
"confirmations": PAYMENT_CHECKOUT.confirmations,
|
"confirmations": PAYMENT_CHECKOUT.confirmations,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
empty_cycles = 0
|
||||||
while True:
|
while True:
|
||||||
|
sleep_sec = interval_sec
|
||||||
try:
|
try:
|
||||||
intents = PAYMENT_CHECKOUT.list_pending_confirm_intents(limit=batch_size)
|
intents = PAYMENT_CHECKOUT.list_pending_confirm_intents(limit=batch_size)
|
||||||
scanned = len(intents)
|
scanned = len(intents)
|
||||||
@@ -149,10 +169,17 @@ def _runner() -> None:
|
|||||||
failed,
|
failed,
|
||||||
)
|
)
|
||||||
_append_audit_event("confirm_loop_cycle", cycle_summary)
|
_append_audit_event("confirm_loop_cycle", cycle_summary)
|
||||||
|
if scanned:
|
||||||
|
empty_cycles = 0
|
||||||
|
else:
|
||||||
|
empty_cycles += 1
|
||||||
|
if empty_cycles >= idle_after_empty_cycles:
|
||||||
|
sleep_sec = idle_interval_sec
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
empty_cycles = 0
|
||||||
logger.warning(f"payment confirm cycle failed: {exc}")
|
logger.warning(f"payment confirm cycle failed: {exc}")
|
||||||
_append_audit_event("confirm_loop_error", {"error": str(exc)})
|
_append_audit_event("confirm_loop_error", {"error": str(exc)})
|
||||||
time.sleep(interval_sec)
|
time.sleep(sleep_sec)
|
||||||
|
|
||||||
|
|
||||||
def start_payment_confirm_loop():
|
def start_payment_confirm_loop():
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.payments import confirm_loop
|
||||||
|
|
||||||
|
|
||||||
|
class _StopLoop(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeCheckout:
|
||||||
|
enabled = True
|
||||||
|
chain_id = 137
|
||||||
|
confirmations = 2
|
||||||
|
|
||||||
|
def __init__(self, batches):
|
||||||
|
self._batches = list(batches)
|
||||||
|
self.confirmed = []
|
||||||
|
|
||||||
|
def list_pending_confirm_intents(self, limit):
|
||||||
|
if not self._batches:
|
||||||
|
return []
|
||||||
|
return self._batches.pop(0)
|
||||||
|
|
||||||
|
def confirm_intent_tx(self, *, user_id, intent_id, tx_hash):
|
||||||
|
self.confirmed.append(
|
||||||
|
{"user_id": user_id, "intent_id": intent_id, "tx_hash": tx_hash}
|
||||||
|
)
|
||||||
|
return {"already_confirmed": True}
|
||||||
|
|
||||||
|
|
||||||
|
def _run_until_sleep_count(monkeypatch, checkout, sleep_count):
|
||||||
|
sleeps = []
|
||||||
|
|
||||||
|
monkeypatch.setenv("POLYWEATHER_PAYMENT_CONFIRM_LOOP_ENABLED", "true")
|
||||||
|
|
||||||
|
def fake_sleep(seconds):
|
||||||
|
sleeps.append(seconds)
|
||||||
|
if len(sleeps) >= sleep_count:
|
||||||
|
raise _StopLoop
|
||||||
|
|
||||||
|
monkeypatch.setattr(confirm_loop, "PAYMENT_CHECKOUT", checkout)
|
||||||
|
monkeypatch.setattr(confirm_loop, "_append_audit_event", lambda *_args: None)
|
||||||
|
monkeypatch.setattr(confirm_loop.time, "sleep", fake_sleep)
|
||||||
|
|
||||||
|
with pytest.raises(_StopLoop):
|
||||||
|
confirm_loop._runner()
|
||||||
|
|
||||||
|
return sleeps
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_loop_uses_idle_interval_after_repeated_empty_cycles(monkeypatch):
|
||||||
|
monkeypatch.setenv("POLYWEATHER_PAYMENT_CONFIRM_LOOP_INTERVAL_SEC", "20")
|
||||||
|
monkeypatch.setenv("POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_INTERVAL_SEC", "300")
|
||||||
|
monkeypatch.setenv("POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_AFTER_EMPTY_CYCLES", "2")
|
||||||
|
|
||||||
|
sleeps = _run_until_sleep_count(
|
||||||
|
monkeypatch,
|
||||||
|
_FakeCheckout(batches=[[], []]),
|
||||||
|
sleep_count=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert sleeps == [20, 300]
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_loop_returns_to_active_interval_when_intent_is_found(monkeypatch):
|
||||||
|
monkeypatch.setenv("POLYWEATHER_PAYMENT_CONFIRM_LOOP_INTERVAL_SEC", "20")
|
||||||
|
monkeypatch.setenv("POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_INTERVAL_SEC", "300")
|
||||||
|
monkeypatch.setenv("POLYWEATHER_PAYMENT_CONFIRM_LOOP_IDLE_AFTER_EMPTY_CYCLES", "1")
|
||||||
|
checkout = _FakeCheckout(
|
||||||
|
batches=[
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"intent_id": "intent-1",
|
||||||
|
"user_id": "user-1",
|
||||||
|
"tx_hash": "0x" + "a" * 64,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
sleeps = _run_until_sleep_count(monkeypatch, checkout, sleep_count=2)
|
||||||
|
|
||||||
|
assert sleeps == [300, 20]
|
||||||
|
assert checkout.confirmed == [
|
||||||
|
{
|
||||||
|
"intent_id": "intent-1",
|
||||||
|
"user_id": "user-1",
|
||||||
|
"tx_hash": "0x" + "a" * 64,
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user