feat: add gateway client with env-driven assembly
Includes config aggregation for multi-source env keys, from_env and from_settings factories with explicit shared-backend injection, gather_bounded, top-level exports, tightened import-linter layers with the gate removed from the Makefile, and the finalized .env.example.
This commit is contained in:
@@ -51,8 +51,13 @@ class InMemoryGate:
|
||||
g.probe_owner = owner
|
||||
g.probe_expires = self._now() + self._cfg.probe_ttl_s
|
||||
return GateDecision(
|
||||
source_name=source_name, allowed=True, state=GateState.HALF_OPEN,
|
||||
epoch=g.epoch, is_probe=True, probe_owner=owner, retry_after_s=0.0,
|
||||
source_name=source_name,
|
||||
allowed=True,
|
||||
state=GateState.HALF_OPEN,
|
||||
epoch=g.epoch,
|
||||
is_probe=True,
|
||||
probe_owner=owner,
|
||||
retry_after_s=0.0,
|
||||
)
|
||||
|
||||
async def try_enter(self, source_name: str, owner: str) -> GateDecision:
|
||||
@@ -63,23 +68,36 @@ class InMemoryGate:
|
||||
now = self._now()
|
||||
if g.state is GateState.CLOSED:
|
||||
return GateDecision(
|
||||
source_name=source_name, allowed=True, state=GateState.CLOSED,
|
||||
epoch=g.epoch, is_probe=False, probe_owner=None, retry_after_s=0.0,
|
||||
source_name=source_name,
|
||||
allowed=True,
|
||||
state=GateState.CLOSED,
|
||||
epoch=g.epoch,
|
||||
is_probe=False,
|
||||
probe_owner=None,
|
||||
retry_after_s=0.0,
|
||||
)
|
||||
if g.state is GateState.OPEN:
|
||||
if now >= g.open_until:
|
||||
return self._grant_probe(g, source_name, owner)
|
||||
return GateDecision(
|
||||
source_name=source_name, allowed=False, state=GateState.OPEN,
|
||||
epoch=g.epoch, is_probe=False, probe_owner=None,
|
||||
source_name=source_name,
|
||||
allowed=False,
|
||||
state=GateState.OPEN,
|
||||
epoch=g.epoch,
|
||||
is_probe=False,
|
||||
probe_owner=None,
|
||||
retry_after_s=g.open_until - now,
|
||||
)
|
||||
# HALF_OPEN: 探针在途;租约过期则接管,否则拒绝(防惊群)
|
||||
if now >= g.probe_expires:
|
||||
return self._grant_probe(g, source_name, owner)
|
||||
return GateDecision(
|
||||
source_name=source_name, allowed=False, state=GateState.HALF_OPEN,
|
||||
epoch=g.epoch, is_probe=False, probe_owner=None,
|
||||
source_name=source_name,
|
||||
allowed=False,
|
||||
state=GateState.HALF_OPEN,
|
||||
epoch=g.epoch,
|
||||
is_probe=False,
|
||||
probe_owner=None,
|
||||
retry_after_s=g.probe_expires - now,
|
||||
)
|
||||
|
||||
@@ -96,8 +114,13 @@ class InMemoryGate:
|
||||
|
||||
def _snapshot(self, g: _SourceGate, applied: bool) -> GateUpdate:
|
||||
return GateUpdate(
|
||||
applied=applied, state=g.state, epoch=g.epoch, failure_count=g.fails,
|
||||
retry_after_s=max(0.0, g.open_until - self._now()) if g.state is GateState.OPEN else 0.0,
|
||||
applied=applied,
|
||||
state=g.state,
|
||||
epoch=g.epoch,
|
||||
failure_count=g.fails,
|
||||
retry_after_s=max(0.0, g.open_until - self._now())
|
||||
if g.state is GateState.OPEN
|
||||
else 0.0,
|
||||
)
|
||||
|
||||
def _open(self, g: _SourceGate, reason: str) -> None:
|
||||
@@ -118,7 +141,9 @@ class InMemoryGate:
|
||||
g.probe_expires = 0.0
|
||||
return self._snapshot(g, applied=True)
|
||||
|
||||
async def record_failure(self, entry: GateDecision, reason: str, force_open: bool) -> GateUpdate:
|
||||
async def record_failure(
|
||||
self, entry: GateDecision, reason: str, force_open: bool
|
||||
) -> GateUpdate:
|
||||
g = self._gate(entry.source_name)
|
||||
if not self._fenced(g, entry):
|
||||
return self._snapshot(g, applied=False)
|
||||
|
||||
Reference in New Issue
Block a user