Grade muse-glimmer-28b full 7-prompt battery — new benchmark leader
7 entries (30→37 total). Muse Glimmer 28B (GGUF) avg 80.7 — the strongest model in the benchmark, 6/7 prompts Minor Logic Flaws: lfu 76 | webhook 81 | automation 89 | rust 85 | data 88 | tts 58 | mcp 88 Standout results: - rust 85 (KAT 36, Qwen3-Coder 54) — real tokio channels (mpsc::channel, not hallucinated mpsc::bounded), two-tier CancellationToken, zero clippy lints; one-line E0507 compile fix. - automation 89 — first model to print a correct summary (98/2/0/100); atomic temp+fsync+rename checkpointing. - data 88 edges out Gemma-26B's 86; mcp 88 sets the bar on a new prompt. Only weak spot: tts 58 (backpressure raises instead of awaits, like Qwen3-Coder). Captured via the native /api/v1/chat fix (real tok/sec + TTFT). Slow deep-thinker: ~17-19 t/s, 5-9 min/prompt, ~5-9k tokens incl. reasoning. Also gitignore checkpoint.json (automation test runtime artifact). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"model_instance_id": "google/gemma-4-26b-a4b",
|
||||
"model_instance_id": "meta/muse-glimmer",
|
||||
"stats": {
|
||||
"input_tokens": 718,
|
||||
"total_output_tokens": 5153,
|
||||
"reasoning_output_tokens": 1919,
|
||||
"tokens_per_second": 53.47535329547263,
|
||||
"time_to_first_token_seconds": 1.127
|
||||
"input_tokens": 495,
|
||||
"total_output_tokens": 4749,
|
||||
"reasoning_output_tokens": 3228,
|
||||
"tokens_per_second": 20.201965201572563,
|
||||
"time_to_first_token_seconds": 2.571259
|
||||
},
|
||||
"response_id": "resp_d8fafc117fa4e51b927d8cd531ded49984ae59e49c39d3eb",
|
||||
"response_id": "resp_a37c5d6bb520db613250881b4919be086d4aed4a8f3a078a",
|
||||
"_output_types": [
|
||||
"reasoning",
|
||||
"message"
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Async batch processor with checkpointing, idempotency and graceful SIGINT.
|
||||
Stdlib only, Python 3.11+.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
import random
|
||||
|
||||
CHECKPOINT_PATH = "checkpoint.json"
|
||||
|
||||
class ProcessingError(Exception):
|
||||
pass
|
||||
|
||||
async def process(item: str) -> str:
|
||||
"""Flaky external call ~80% success, 10-80ms latency."""
|
||||
await asyncio.sleep(random.uniform(0.01, 0.08))
|
||||
if random.random() < 0.2:
|
||||
raise ProcessingError(f"flaky failure for {item}")
|
||||
return f"ok:{item}"
|
||||
|
||||
def load_checkpoint():
|
||||
if not os.path.exists(CHECKPOINT_PATH):
|
||||
return set(), set()
|
||||
try:
|
||||
with open(CHECKPOINT_PATH, "r") as f:
|
||||
data = json.load(f)
|
||||
return set(data.get("completed", [])), set(data.get("failed", []))
|
||||
except Exception:
|
||||
return set(), set()
|
||||
|
||||
def save_checkpoint(completed, failed):
|
||||
data = {"completed": list(completed), "failed": list(failed)}
|
||||
tmp = CHECKPOINT_PATH + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(data, f)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp, CHECKPOINT_PATH) # atomic
|
||||
|
||||
async def handle_item(item, completed, failed, lock, sem, stop_event):
|
||||
async with sem:
|
||||
if stop_event.is_set():
|
||||
return
|
||||
backoff = 0.1
|
||||
for attempt in range(3):
|
||||
try:
|
||||
await process(item)
|
||||
async with lock:
|
||||
if item not in completed and item not in failed:
|
||||
completed.add(item)
|
||||
save_checkpoint(completed, failed)
|
||||
return
|
||||
except ProcessingError:
|
||||
if attempt == 2:
|
||||
async with lock:
|
||||
if item not in completed and item not in failed:
|
||||
failed.add(item)
|
||||
save_checkpoint(completed, failed)
|
||||
return
|
||||
await asyncio.sleep(backoff)
|
||||
backoff *= 2
|
||||
|
||||
async def run_batch(items):
|
||||
completed, failed = load_checkpoint()
|
||||
initially_done = len(completed) + len(failed)
|
||||
pending = [i for i in items if i not in completed and i not in failed]
|
||||
|
||||
stop_event = asyncio.Event()
|
||||
lock = asyncio.Lock()
|
||||
sem = asyncio.Semaphore(8)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
def _sigint():
|
||||
if not stop_event.is_set():
|
||||
stop_event.set()
|
||||
try:
|
||||
loop.add_signal_handler(signal.SIGINT, _sigint)
|
||||
except (NotImplementedError, ValueError):
|
||||
pass
|
||||
|
||||
start = time.monotonic()
|
||||
q = asyncio.Queue()
|
||||
for it in pending:
|
||||
q.put_nowait(it)
|
||||
|
||||
async def worker():
|
||||
while True:
|
||||
if stop_event.is_set() and q.empty():
|
||||
break
|
||||
try:
|
||||
item = await asyncio.wait_for(q.get(), timeout=0.1)
|
||||
except asyncio.TimeoutError:
|
||||
if q.empty():
|
||||
break
|
||||
continue
|
||||
await handle_item(item, completed, failed, lock, sem, stop_event)
|
||||
q.task_done()
|
||||
|
||||
workers = [asyncio.create_task(worker()) for _ in range(8)]
|
||||
await asyncio.gather(*workers, return_exceptions=True)
|
||||
|
||||
elapsed_ms = int((time.monotonic() - start) * 1000)
|
||||
summary = {
|
||||
"succeeded": len(completed),
|
||||
"failed": len(failed),
|
||||
"skipped": initially_done,
|
||||
"total": len(items),
|
||||
"elapsed_ms": elapsed_ms,
|
||||
}
|
||||
print(json.dumps(summary))
|
||||
return summary
|
||||
|
||||
def main():
|
||||
items = [f"job-{i}" for i in range(100)]
|
||||
asyncio.run(run_batch(items))
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Tests
|
||||
async def run_tests():
|
||||
if os.path.exists(CHECKPOINT_PATH):
|
||||
os.remove(CHECKPOINT_PATH)
|
||||
random.seed(0)
|
||||
items = [f"job-{i}" for i in range(50)]
|
||||
|
||||
calls = {}
|
||||
active = 0
|
||||
max_active = 0
|
||||
orig = process
|
||||
|
||||
async def counting(item):
|
||||
nonlocal active, max_active
|
||||
active += 1
|
||||
if active > max_active:
|
||||
max_active = active
|
||||
calls[item] = calls.get(item, 0) + 1
|
||||
try:
|
||||
return await orig(item)
|
||||
finally:
|
||||
active -= 1
|
||||
|
||||
globals()["process"] = counting
|
||||
|
||||
# watcher for checkpoint validity
|
||||
errors = []
|
||||
async def watcher():
|
||||
while True:
|
||||
try:
|
||||
with open(CHECKPOINT_PATH, "r") as f:
|
||||
json.load(f)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
errors.append(e)
|
||||
break
|
||||
await asyncio.sleep(0.001)
|
||||
w = asyncio.create_task(watcher())
|
||||
|
||||
# first partial run -> simulate interruption
|
||||
try:
|
||||
await asyncio.wait_for(run_batch(items), timeout=0.05)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
# second run to finish
|
||||
await run_batch(items)
|
||||
w.cancel()
|
||||
try:
|
||||
await w
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
completed, failed = load_checkpoint()
|
||||
assert len(completed) + len(failed) == len(items), "not all done"
|
||||
assert not errors, f"checkpoint corrupt: {errors}"
|
||||
assert max_active <= 8, f"concurrency exceeded: {max_active}"
|
||||
# idempotency: no item processed again after it was completed/failed
|
||||
# (calls count includes retries, but completed items must not be retried)
|
||||
print("tests passed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "test":
|
||||
asyncio.run(run_tests())
|
||||
else:
|
||||
main()
|
||||
@@ -0,0 +1,216 @@
|
||||
# single file demo - Python 3.11+ asyncio stdlib only
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List, Tuple, Dict, Any
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Mock in-memory DB
|
||||
# ----------------------------------------------------------------------
|
||||
class MockDB:
|
||||
users: Dict[int, Dict[str, Any]] = {}
|
||||
posts: Dict[int, List[Dict[str, Any]]] = {}
|
||||
|
||||
def seed_db():
|
||||
MockDB.users.clear()
|
||||
MockDB.posts.clear()
|
||||
for uid in range(1, 26):
|
||||
MockDB.users[uid] = {"id": uid, "name": f"user{uid}", "label": f"label{uid % 5}"}
|
||||
MockDB.posts[uid] = [
|
||||
{"id": pid, "user_id": uid, "title": f"post{pid} for user{uid}"}
|
||||
for pid in range(1, 6)
|
||||
]
|
||||
|
||||
seed_db()
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Mock pool / connection
|
||||
# ----------------------------------------------------------------------
|
||||
class MockPool:
|
||||
def __init__(self, max_size: int = 5, acquire_timeout: float = 2.0):
|
||||
self.max_size = max_size
|
||||
self.acquire_timeout = acquire_timeout
|
||||
self._sem = asyncio.Semaphore(max_size)
|
||||
self._checked_out = 0
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
async def acquire(self) -> "MockConnection":
|
||||
try:
|
||||
await asyncio.wait_for(self._sem.acquire(), timeout=self.acquire_timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise TimeoutError(f"Pool exhausted after {self.acquire_timeout}s")
|
||||
async with self._lock:
|
||||
self._checked_out += 1
|
||||
return MockConnection(self)
|
||||
|
||||
async def _release(self):
|
||||
async with self._lock:
|
||||
self._checked_out -= 1
|
||||
self._sem.release()
|
||||
|
||||
@property
|
||||
def checked_out(self) -> int:
|
||||
return self._checked_out
|
||||
|
||||
|
||||
class MockConnection:
|
||||
def __init__(self, pool: MockPool):
|
||||
self.pool = pool
|
||||
|
||||
async def fetch(self, query: str, params: List[Any]) -> List[Dict[str, Any]]:
|
||||
await asyncio.sleep(0) # simulate IO
|
||||
if query.startswith("SELECT COUNT"):
|
||||
return [{"count": len(MockDB.users)}]
|
||||
if "FROM users WHERE id =" in query:
|
||||
uid = params[0]
|
||||
user = MockDB.users.get(uid)
|
||||
return [user] if user else []
|
||||
if "FROM users ORDER BY id LIMIT" in query:
|
||||
limit, offset = params
|
||||
sorted_users = [MockDB.users[i] for i in sorted(MockDB.users)]
|
||||
return sorted_users[offset: offset + limit]
|
||||
if "FROM posts WHERE user_id =" in query:
|
||||
uid = params[0]
|
||||
return MockDB.posts.get(uid, [])
|
||||
raise ValueError(f"Unknown fetch query: {query}")
|
||||
|
||||
async def execute(self, query: str, params: List[Any]) -> int:
|
||||
await asyncio.sleep(0)
|
||||
if query.startswith("UPDATE users SET label"):
|
||||
label, uid = params
|
||||
if uid not in MockDB.users:
|
||||
raise KeyError(f"User {uid} not found")
|
||||
MockDB.users[uid]["label"] = label
|
||||
return 1
|
||||
raise ValueError(f"Unknown execute query: {query}")
|
||||
|
||||
async def release(self):
|
||||
await self.pool._release()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_connection(pool: MockPool):
|
||||
conn = await pool.acquire()
|
||||
try:
|
||||
yield conn
|
||||
finally:
|
||||
await conn.release()
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Service API
|
||||
# ----------------------------------------------------------------------
|
||||
class UserService:
|
||||
def __init__(self, pool: MockPool):
|
||||
self.pool = pool
|
||||
|
||||
async def get_users(self, page: int, page_size: int) -> dict:
|
||||
if page < 1:
|
||||
raise ValueError("page >= 1 required")
|
||||
if not 1 <= page_size <= 100:
|
||||
raise ValueError("1 <= page_size <= 100 required")
|
||||
offset = (page - 1) * page_size
|
||||
async with get_connection(self.pool) as conn:
|
||||
total_row = await conn.fetch("SELECT COUNT(*) FROM users", [])
|
||||
items = await conn.fetch(
|
||||
"SELECT id, name, label FROM users ORDER BY id LIMIT $1 OFFSET $2",
|
||||
[page_size, offset],
|
||||
)
|
||||
return {"items": items, "page": page, "page_size": page_size, "total": total_row[0]["count"]}
|
||||
|
||||
async def get_user_with_posts(self, user_id: int) -> dict:
|
||||
async with get_connection(self.pool) as conn:
|
||||
user_rows = await conn.fetch(
|
||||
"SELECT id, name, label FROM users WHERE id = $1", [user_id]
|
||||
)
|
||||
if not user_rows:
|
||||
raise KeyError(f"User {user_id} not found")
|
||||
user = user_rows[0]
|
||||
posts = await conn.fetch(
|
||||
"SELECT id, title FROM posts WHERE user_id = $1", [user_id]
|
||||
)
|
||||
return {"user": user, "posts": posts}
|
||||
|
||||
async def relabel_users(self, pairs: List[Tuple[int, str]]) -> int:
|
||||
if not pairs:
|
||||
return 0
|
||||
async with get_connection(self.pool) as conn:
|
||||
# snapshot for rollback
|
||||
snapshot = {}
|
||||
for uid, _ in pairs:
|
||||
if uid in MockDB.users:
|
||||
snapshot[uid] = MockDB.users[uid]["label"]
|
||||
try:
|
||||
for uid, label in pairs:
|
||||
await conn.execute(
|
||||
"UPDATE users SET label = $1 WHERE id = $2", [label, uid]
|
||||
)
|
||||
return len(pairs)
|
||||
except Exception:
|
||||
# rollback to original labels
|
||||
for uid, orig in snapshot.items():
|
||||
MockDB.users[uid]["label"] = orig
|
||||
return 0
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Tests
|
||||
# ----------------------------------------------------------------------
|
||||
async def run_tests():
|
||||
pool = MockPool(max_size=5, acquire_timeout=2.0)
|
||||
service = UserService(pool)
|
||||
|
||||
# a) no leak over 100 calls
|
||||
for _ in range(100):
|
||||
await service.get_users(page=1, page_size=10)
|
||||
assert pool.checked_out == 0, "leak detected"
|
||||
print("✓ a) 100 calls, pool checked_out = 0")
|
||||
|
||||
# b) pagination correctness
|
||||
res1 = await service.get_users(page=1, page_size=10)
|
||||
assert res1["total"] == 25
|
||||
assert len(res1["items"]) == 10
|
||||
res3 = await service.get_users(page=3, page_size=10)
|
||||
assert len(res3["items"]) == 5 # remainder
|
||||
res4 = await service.get_users(page=4, page_size=10)
|
||||
assert len(res4["items"]) == 0
|
||||
print("✓ b) pagination math correct")
|
||||
|
||||
# c) transactional bulk rollback
|
||||
original = {uid: MockDB.users[uid]["label"] for uid in (1, 2, 3)}
|
||||
pairs = [(1, "new1"), (2, "new2"), (999, "bad")]
|
||||
result = await service.relabel_users(pairs)
|
||||
assert result == 0
|
||||
for uid in (1, 2):
|
||||
assert MockDB.users[uid]["label"] == original[uid], "rollback failed"
|
||||
print("✓ c) relabel_users rolls back on bad id")
|
||||
|
||||
# d) concurrent access without exhaustion
|
||||
tasks = [service.get_user_with_posts(uid) for uid in range(1, 11)]
|
||||
results = await asyncio.gather(*tasks)
|
||||
assert len(results) == 10
|
||||
assert pool.checked_out == 0
|
||||
print("✓ d) 10 concurrent get_user_with_posts, no exhaustion")
|
||||
|
||||
# validation & missing user
|
||||
try:
|
||||
await service.get_users(page=0, page_size=10)
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
await service.get_user_with_posts(9999)
|
||||
assert False
|
||||
except KeyError:
|
||||
pass
|
||||
print("✓ validation & missing user errors")
|
||||
|
||||
# injection safety demo - params are never interpolated
|
||||
await service.relabel_users([(1, "label'; DROP TABLE users;--")])
|
||||
assert MockDB.users[1]["label"] == "label'; DROP TABLE users;--"
|
||||
print("✓ parameterized queries prevent injection")
|
||||
|
||||
print("\nAll tests passed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_tests())
|
||||
@@ -0,0 +1,270 @@
|
||||
# lfu_cache.py
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Any, Optional, Dict, Set
|
||||
|
||||
class _Node:
|
||||
__slots__ = ("key", "value", "expire_at", "freq", "prev", "next")
|
||||
def __init__(self, key=None, value=None, expire_at=None, freq=0):
|
||||
self.key = key
|
||||
self.value = value
|
||||
self.expire_at = expire_at
|
||||
self.freq = freq
|
||||
self.prev = None
|
||||
self.next = None
|
||||
|
||||
|
||||
class _FreqList:
|
||||
__slots__ = ("head", "tail")
|
||||
def __init__(self):
|
||||
self.head = _Node()
|
||||
self.tail = _Node()
|
||||
self.head.next = self.tail
|
||||
self.tail.prev = self.head
|
||||
|
||||
|
||||
class LFUCache:
|
||||
def __init__(self, capacity: int, evict_interval: float = 0.5):
|
||||
self.capacity = capacity
|
||||
self.key_map: Dict[Any, _Node] = {}
|
||||
self.freq_map: Dict[int, _FreqList] = {}
|
||||
self.min_freq = 0
|
||||
self.lock = asyncio.Lock()
|
||||
self._evictor_task: Optional[asyncio.Task] = None
|
||||
self._evict_interval = evict_interval
|
||||
|
||||
# ---------- internal helpers ----------
|
||||
def _update_min_freq(self):
|
||||
while self.min_freq not in self.freq_map:
|
||||
self.min_freq += 1
|
||||
|
||||
def _add_node_to_head(self, freq: int, node: _Node):
|
||||
lst = self.freq_map.get(freq)
|
||||
if lst is None:
|
||||
lst = _FreqList()
|
||||
self.freq_map[freq] = lst
|
||||
node.prev = lst.head
|
||||
node.next = lst.head.next
|
||||
lst.head.next.prev = node
|
||||
lst.head.next = node
|
||||
|
||||
def _remove_node(self, node: _Node):
|
||||
freq = node.freq
|
||||
lst = self.freq_map.get(freq)
|
||||
if lst:
|
||||
node.prev.next = node.next
|
||||
node.next.prev = node.prev
|
||||
if lst.head.next is lst.tail:
|
||||
del self.freq_map[freq]
|
||||
if self.min_freq == freq:
|
||||
self._update_min_freq()
|
||||
if self.key_map.get(node.key) is node:
|
||||
del self.key_map[node.key]
|
||||
|
||||
def _increment_freq(self, node: _Node):
|
||||
old_freq = node.freq
|
||||
lst = self.freq_map[old_freq]
|
||||
node.prev.next = node.next
|
||||
node.next.prev = node.prev
|
||||
if lst.head.next is lst.tail:
|
||||
del self.freq_map[old_freq]
|
||||
if self.min_freq == old_freq:
|
||||
self._update_min_freq()
|
||||
new_freq = old_freq + 1
|
||||
node.freq = new_freq
|
||||
self._add_node_to_head(new_freq, node)
|
||||
|
||||
def _evict_one(self):
|
||||
if not self.freq_map:
|
||||
return
|
||||
if self.min_freq not in self.freq_map:
|
||||
self._update_min_freq()
|
||||
lst = self.freq_map[self.min_freq]
|
||||
node = lst.tail.prev
|
||||
if node is lst.head:
|
||||
return
|
||||
self._remove_node(node)
|
||||
|
||||
def _is_expired(self, node: _Node) -> bool:
|
||||
return node.expire_at is not None and time.monotonic() > node.expire_at
|
||||
|
||||
# ---------- public API ----------
|
||||
async def get(self, key: Any) -> Optional[Any]:
|
||||
async with self.lock:
|
||||
node = self.key_map.get(key)
|
||||
if not node:
|
||||
return None
|
||||
if self._is_expired(node):
|
||||
self._remove_node(node)
|
||||
return None
|
||||
self._increment_freq(node)
|
||||
return node.value
|
||||
|
||||
async def put(self, key: Any, value: Any, ttl_seconds: Optional[float] = None):
|
||||
async with self.lock:
|
||||
now = time.monotonic()
|
||||
expire_at = now + ttl_seconds if ttl_seconds is not None else None
|
||||
node = self.key_map.get(key)
|
||||
if node:
|
||||
if self._is_expired(node):
|
||||
self._remove_node(node)
|
||||
node = None
|
||||
if node:
|
||||
node.value = value
|
||||
node.expire_at = expire_at
|
||||
self._increment_freq(node)
|
||||
return
|
||||
if len(self.key_map) >= self.capacity:
|
||||
self._evict_one()
|
||||
node = _Node(key=key, value=value, expire_at=expire_at, freq=1)
|
||||
self.key_map[key] = node
|
||||
self._add_node_to_head(1, node)
|
||||
if self.min_freq == 0 or 1 < self.min_freq:
|
||||
self.min_freq = 1
|
||||
|
||||
def begin_transaction(self) -> "Transaction":
|
||||
return Transaction(self)
|
||||
|
||||
async def start_evictor(self):
|
||||
if self._evictor_task and not self._evictor_task.done():
|
||||
return
|
||||
self._evictor_task = asyncio.create_task(self._evictor_loop())
|
||||
|
||||
async def stop_evictor(self):
|
||||
if self._evictor_task:
|
||||
self._evictor_task.cancel()
|
||||
try:
|
||||
await self._evictor_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._evictor_task = None
|
||||
|
||||
async def _evictor_loop(self):
|
||||
while True:
|
||||
await asyncio.sleep(self._evict_interval)
|
||||
async with self.lock:
|
||||
now = time.monotonic()
|
||||
batch = 100
|
||||
count = 0
|
||||
for k in list(self.key_map.keys()):
|
||||
if count >= batch:
|
||||
break
|
||||
node = self.key_map.get(k)
|
||||
if node and node.expire_at and now > node.expire_at:
|
||||
self._remove_node(node)
|
||||
count += 1
|
||||
|
||||
|
||||
class Transaction:
|
||||
def __init__(self, cache: LFUCache):
|
||||
self.cache = cache
|
||||
self.pending_puts: Dict[Any, tuple[Any, Optional[float]]] = {}
|
||||
self.pending_deletes: Set[Any] = set()
|
||||
|
||||
async def get(self, key: Any) -> Optional[Any]:
|
||||
if key in self.pending_deletes:
|
||||
return None
|
||||
if key in self.pending_puts:
|
||||
val, _ = self.pending_puts[key]
|
||||
return val
|
||||
async with self.cache.lock:
|
||||
node = self.cache.key_map.get(key)
|
||||
if not node:
|
||||
return None
|
||||
if node.expire_at and time.monotonic() > node.expire_at:
|
||||
self.cache._remove_node(node)
|
||||
return None
|
||||
return node.value
|
||||
|
||||
async def put(self, key: Any, value: Any, ttl_seconds: Optional[float] = None):
|
||||
self.pending_puts[key] = (value, ttl_seconds)
|
||||
self.pending_deletes.discard(key)
|
||||
|
||||
async def delete(self, key: Any):
|
||||
self.pending_deletes.add(key)
|
||||
self.pending_puts.pop(key, None)
|
||||
|
||||
async def commit(self):
|
||||
async with self.cache.lock:
|
||||
for key in self.pending_deletes:
|
||||
node = self.cache.key_map.get(key)
|
||||
if node:
|
||||
self.cache._remove_node(node)
|
||||
for key, (value, ttl_seconds) in self.pending_puts.items():
|
||||
expire_at = None
|
||||
if ttl_seconds is not None:
|
||||
expire_at = time.monotonic() + ttl_seconds
|
||||
node = self.cache.key_map.get(key)
|
||||
if node:
|
||||
node.value = value
|
||||
node.expire_at = expire_at
|
||||
self.cache._increment_freq(node)
|
||||
else:
|
||||
if len(self.cache.key_map) >= self.cache.capacity:
|
||||
self.cache._evict_one()
|
||||
node = _Node(key=key, value=value, expire_at=expire_at, freq=1)
|
||||
self.cache.key_map[key] = node
|
||||
self.cache._add_node_to_head(1, node)
|
||||
if self.cache.min_freq == 0 or 1 < self.cache.min_freq:
|
||||
self.cache.min_freq = 1
|
||||
self.pending_puts.clear()
|
||||
self.pending_deletes.clear()
|
||||
|
||||
def rollback(self):
|
||||
self.pending_puts.clear()
|
||||
self.pending_deletes.clear()
|
||||
|
||||
|
||||
# ------------------- tests -------------------
|
||||
async def main():
|
||||
cache = LFUCache(capacity=3, evict_interval=0.2)
|
||||
await cache.start_evictor()
|
||||
print("=== LFU eviction order ===")
|
||||
await cache.put("a", 1)
|
||||
await cache.put("b", 2)
|
||||
await cache.put("c", 3)
|
||||
await cache.get("a")
|
||||
await cache.get("a")
|
||||
await cache.get("b")
|
||||
await cache.put("d", 4) # should evict c
|
||||
assert await cache.get("c") is None
|
||||
assert await cache.get("a") == 1
|
||||
print("LFU eviction OK")
|
||||
|
||||
print("=== TTL lazy vs background ===")
|
||||
await cache.put("x", "temp", ttl_seconds=0.1)
|
||||
await asyncio.sleep(0.15)
|
||||
assert await cache.get("x") is None
|
||||
await cache.put("y", "temp2", ttl_seconds=0.1)
|
||||
await asyncio.sleep(0.3)
|
||||
assert await cache.get("y") is None
|
||||
print("TTL OK")
|
||||
|
||||
print("=== Transaction commit / rollback ===")
|
||||
tx = cache.begin_transaction()
|
||||
await tx.put("t1", 100)
|
||||
assert await cache.get("t1") is None
|
||||
await tx.commit()
|
||||
assert await cache.get("t1") == 100
|
||||
|
||||
tx2 = cache.begin_transaction()
|
||||
await tx2.put("t2", 200)
|
||||
await tx2.rollback()
|
||||
assert await cache.get("t2") is None
|
||||
print("Transaction OK")
|
||||
|
||||
print("=== Stress test 50 concurrent tasks ===")
|
||||
async def worker(i):
|
||||
for j in range(20):
|
||||
k = f"k{i%10}"
|
||||
await cache.put(k, i*j, ttl_seconds=5)
|
||||
await cache.get(k)
|
||||
await asyncio.gather(*(worker(i) for i in range(50)))
|
||||
print("Stress OK")
|
||||
|
||||
await cache.stop_evictor()
|
||||
print("All tests passed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// mcp-jsonplaceholder.ts
|
||||
// Run: npx tsx mcp-jsonplaceholder.ts
|
||||
// Test: npx tsx mcp-jsonplaceholder.ts --test
|
||||
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
const BASE = 'https://jsonplaceholder.typicode.com';
|
||||
|
||||
// ---------- Validation schemas ----------
|
||||
const GetUserSchema = z.object({
|
||||
id: z.number().int().positive()
|
||||
});
|
||||
|
||||
const ListPostsSchema = z.object({
|
||||
user_id: z.number().int().positive(),
|
||||
limit: z.number().int().min(1).max(100).optional().default(10)
|
||||
});
|
||||
|
||||
const SearchPostsSchema = z.object({
|
||||
query: z.string().min(1).max(200)
|
||||
});
|
||||
|
||||
// ---------- HTTP helper with timeout ----------
|
||||
async function fetchJson(url: string, timeoutMs = 8000) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(url, { signal: controller.signal });
|
||||
clearTimeout(timer);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
clearTimeout(timer);
|
||||
if (err.name === 'AbortError') throw new Error('Request timed out');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Typed implementations ----------
|
||||
async function getUserImpl(id: number) {
|
||||
const user = await fetchJson(`${BASE}/users/${id}`);
|
||||
if (!user || !user.id) throw new Error(`User ${id} not found`);
|
||||
return user;
|
||||
}
|
||||
|
||||
async function listPostsImpl(user_id: number, limit: number) {
|
||||
const posts = await fetchJson(`${BASE}/posts?userId=${user_id}`);
|
||||
if (!Array.isArray(posts)) throw new Error('Invalid posts response');
|
||||
return posts.slice(0, limit).map(p => ({ id: p.id, userId: p.userId, title: p.title, body: p.body }));
|
||||
}
|
||||
|
||||
async function searchPostsImpl(query: string) {
|
||||
const posts = await fetchJson(`${BASE}/posts`);
|
||||
const q = query.toLowerCase();
|
||||
const filtered = posts.filter((p: any) => p.title.toLowerCase().includes(q));
|
||||
return filtered.map((p: any) => ({ id: p.id, userId: p.userId, title: p.title }));
|
||||
}
|
||||
|
||||
// ---------- MCP Server ----------
|
||||
const server = new Server({ name: 'jsonplaceholder-mcp', version: '1.0.0' }, {
|
||||
capabilities: { tools: {} }
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [
|
||||
{
|
||||
name: 'get_user',
|
||||
description: 'Fetch a single user by numeric id',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: { id: { type: 'integer', minimum: 1 } },
|
||||
required: ['id'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'list_posts_by_user',
|
||||
description: 'Fetch posts for a user with optional limit',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user_id: { type: 'integer', minimum: 1 },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100 }
|
||||
},
|
||||
required: ['user_id'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_posts',
|
||||
description: 'Search posts by title substring, case-insensitive',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: { query: { type: 'string', minLength: 1, maxLength: 200 } },
|
||||
required: ['query'],
|
||||
additionalProperties: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
try {
|
||||
switch (name) {
|
||||
case 'get_user': {
|
||||
const { id } = GetUserSchema.parse(args);
|
||||
const user = await getUserImpl(id);
|
||||
return { content: [{ type: 'text', text: JSON.stringify(user) }] };
|
||||
}
|
||||
case 'list_posts_by_user': {
|
||||
const { user_id, limit } = ListPostsSchema.parse(args);
|
||||
const posts = await listPostsImpl(user_id, limit);
|
||||
return { content: [{ type: 'text', text: JSON.stringify(posts) }] };
|
||||
}
|
||||
case 'search_posts': {
|
||||
const { query } = SearchPostsSchema.parse(args);
|
||||
const results = await searchPostsImpl(query);
|
||||
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown tool ${name}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function startServer() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('JSONPlaceholder MCP server running on stdio');
|
||||
}
|
||||
|
||||
// ---------- Tests ----------
|
||||
async function runTests() {
|
||||
console.log('=== MCP JSONPlaceholder Tests ===');
|
||||
// a) Happy path
|
||||
try {
|
||||
const user = await getUserImpl(1);
|
||||
console.log('a) get_user(1) ->', user.id === 1 && !!user.name ? 'PASS' : 'FAIL', user.name);
|
||||
} catch (e: any) {
|
||||
console.log('a) FAIL', e.message);
|
||||
}
|
||||
|
||||
// b) 404 / not-found handled
|
||||
try {
|
||||
await getUserImpl(9999);
|
||||
console.log('b) 404 handled -> PASS (empty)');
|
||||
} catch (e: any) {
|
||||
console.log('b) 404 handled -> PASS', e.message);
|
||||
}
|
||||
|
||||
// c) Validation rejects malformed input
|
||||
try { GetUserSchema.parse({ id: -5 }); console.log('c1) FAIL'); }
|
||||
catch { console.log('c1) get_user(-5) rejected -> PASS'); }
|
||||
|
||||
try { ListPostsSchema.parse({ user_id: 'x' }); console.log('c2) FAIL'); }
|
||||
catch { console.log('c2) list_posts_by_user("x") rejected -> PASS'); }
|
||||
|
||||
try { SearchPostsSchema.parse({ query: '' }); console.log('c3) FAIL'); }
|
||||
catch { console.log('c3) search_posts("") rejected -> PASS'); }
|
||||
|
||||
console.log('Tests done');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (process.argv.includes('--test')) {
|
||||
runTests();
|
||||
} else {
|
||||
startServer();
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
// Cargo.toml
|
||||
// tokio = { version = "1", features = ["full"] }
|
||||
// tokio-util = "0.7"
|
||||
// serde = { version = "1", features = ["derive"] }
|
||||
// thiserror = "1"
|
||||
// rand = "0.9"
|
||||
|
||||
use rand::Rng;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum FetchError {
|
||||
#[error("transient")]
|
||||
Transient,
|
||||
#[error("always fail")]
|
||||
AlwaysFail,
|
||||
}
|
||||
|
||||
async fn mock_fetch(id: u32) -> Result<Vec<String>, FetchError> {
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
if id == 999 {
|
||||
return Err(FetchError::AlwaysFail);
|
||||
}
|
||||
let mut rng = rand::rng();
|
||||
if rng.random_bool(0.15) {
|
||||
Err(FetchError::Transient)
|
||||
} else {
|
||||
Ok((0..rng.random_range(0..4))
|
||||
.map(|i| format!("item-{}-{}", id, i))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct Item {
|
||||
watcher_id: u32,
|
||||
items: Vec<String>,
|
||||
ts: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Default, Clone)]
|
||||
struct Aggregated {
|
||||
items: Vec<Item>,
|
||||
total_count: usize,
|
||||
}
|
||||
|
||||
struct WatcherMeta {
|
||||
token: CancellationToken,
|
||||
handle: JoinHandle<()>,
|
||||
consecutive_failures: Arc<Mutex<u32>>,
|
||||
healthy: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
pub struct WatcherManager {
|
||||
pub(crate) tx: mpsc::Sender<Item>,
|
||||
pub(crate) aggregated: Arc<Mutex<Aggregated>>,
|
||||
pub(crate) watchers: Arc<Mutex<HashMap<u32, WatcherMeta>>>,
|
||||
pub(crate) next_id: Arc<Mutex<u32>>,
|
||||
pub(crate) shutdown_token: CancellationToken,
|
||||
pub(crate) consumer_handle: JoinHandle<Aggregated>,
|
||||
}
|
||||
|
||||
impl WatcherManager {
|
||||
pub async fn new(num_watchers: usize) -> Self {
|
||||
let (tx, rx) = mpsc::channel(32); // bounded -> backpressure
|
||||
let aggregated = Arc::new(Mutex::new(Aggregated::default()));
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let watchers = Arc::new(Mutex::new(HashMap::new()));
|
||||
let next_id = Arc::new(Mutex::new(1));
|
||||
let consumer_handle = tokio::spawn(consumer_task(rx, shutdown_token.clone(), aggregated.clone()));
|
||||
|
||||
let manager = WatcherManager {
|
||||
tx,
|
||||
aggregated,
|
||||
watchers,
|
||||
next_id,
|
||||
shutdown_token,
|
||||
consumer_handle,
|
||||
};
|
||||
|
||||
for _ in 0..num_watchers {
|
||||
manager.add_watcher().await;
|
||||
}
|
||||
manager
|
||||
}
|
||||
|
||||
pub async fn add_watcher(&self) -> u32 {
|
||||
let mut id_guard = self.next_id.lock().await;
|
||||
let id = *id_guard;
|
||||
*id_guard += 1;
|
||||
drop(id_guard);
|
||||
self.add_watcher_with_id(id).await
|
||||
}
|
||||
|
||||
pub async fn add_watcher_with_id(&self, id: u32) -> u32 {
|
||||
let token = CancellationToken::new();
|
||||
let tx = self.tx.clone();
|
||||
let shutdown_token = self.shutdown_token.clone();
|
||||
let consecutive_failures = Arc::new(Mutex::new(0u32));
|
||||
let healthy = Arc::new(Mutex::new(true));
|
||||
|
||||
let handle = tokio::spawn(watcher_task(
|
||||
id,
|
||||
token.clone(),
|
||||
shutdown_token,
|
||||
tx,
|
||||
consecutive_failures.clone(),
|
||||
healthy.clone(),
|
||||
));
|
||||
|
||||
let meta = WatcherMeta {
|
||||
token,
|
||||
handle,
|
||||
consecutive_failures,
|
||||
healthy,
|
||||
};
|
||||
self.watchers.lock().await.insert(id, meta);
|
||||
id
|
||||
}
|
||||
|
||||
pub async fn remove_watcher(&self, id: u32) -> Result<(), String> {
|
||||
let mut watchers = self.watchers.lock().await;
|
||||
if let Some(meta) = watchers.remove(&id) {
|
||||
meta.token.cancel();
|
||||
drop(watchers);
|
||||
let _ = meta.handle.await;
|
||||
Ok(())
|
||||
} else {
|
||||
Err("not found".into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn shutdown(&mut self) -> Aggregated {
|
||||
self.shutdown_token.cancel();
|
||||
let mut watchers = self.watchers.lock().await;
|
||||
let mut handles = Vec::new();
|
||||
for (_, meta) in watchers.drain() {
|
||||
meta.token.cancel();
|
||||
handles.push(meta.handle);
|
||||
}
|
||||
drop(watchers);
|
||||
for h in handles {
|
||||
let _ = h.await;
|
||||
}
|
||||
self.consumer_handle.await.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
async fn watcher_task(
|
||||
id: u32,
|
||||
token: CancellationToken,
|
||||
shutdown_token: CancellationToken,
|
||||
tx: mpsc::Sender<Item>,
|
||||
consecutive_failures: Arc<Mutex<u32>>,
|
||||
healthy: Arc<Mutex<bool>>,
|
||||
) {
|
||||
let mut interval = tokio::time::interval(Duration::from_millis(100));
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = token.cancelled() => break,
|
||||
_ = shutdown_token.cancelled() => break,
|
||||
_ = interval.tick() => {}
|
||||
}
|
||||
match mock_fetch(id).await {
|
||||
Ok(items) => {
|
||||
*consecutive_failures.lock().await = 0;
|
||||
*healthy.lock().await = true;
|
||||
if !items.is_empty() {
|
||||
let item = Item {
|
||||
watcher_id: id,
|
||||
items,
|
||||
ts: tokio::time::Instant::now().elapsed().as_millis() as u64,
|
||||
};
|
||||
// backpressure: await if channel full
|
||||
if tx.send(item).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let mut fails = consecutive_failures.lock().await;
|
||||
*fails += 1;
|
||||
if *fails > 5 {
|
||||
*healthy.lock().await = false;
|
||||
}
|
||||
eprintln!("watcher {} error: {:?}", id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn consumer_task(
|
||||
mut rx: mpsc::Receiver<Item>,
|
||||
shutdown_token: CancellationToken,
|
||||
aggregated: Arc<Mutex<Aggregated>>,
|
||||
) -> Aggregated {
|
||||
let mut agg = Aggregated::default();
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = shutdown_token.cancelled() => break,
|
||||
maybe_item = rx.recv() => {
|
||||
match maybe_item {
|
||||
Some(item) => {
|
||||
agg.items.push(item);
|
||||
agg.total_count += 1;
|
||||
*aggregated.lock().await = agg.clone();
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
agg
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_manager_runs_and_shuts_down() {
|
||||
let mut manager = WatcherManager::new(4).await;
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
let agg = manager.shutdown().await;
|
||||
assert!(agg.total_count > 0, "consumer should have received items");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_unhealthy_watcher() {
|
||||
let mut manager = WatcherManager::new(0).await;
|
||||
let id = manager.add_watcher_with_id(999).await;
|
||||
tokio::time::sleep(Duration::from_millis(800)).await;
|
||||
let watchers = manager.watchers.lock().await;
|
||||
let meta = watchers.get(&id).expect("watcher exists");
|
||||
let healthy = *meta.healthy.lock().await;
|
||||
assert!(!healthy, "watcher should be marked unhealthy after >5 failures");
|
||||
drop(watchers);
|
||||
let _ = manager.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_remove_concurrent() {
|
||||
use std::sync::Arc;
|
||||
let manager = Arc::new(Mutex::new(WatcherManager::new(0).await));
|
||||
let mut handles = Vec::new();
|
||||
for _ in 0..20 {
|
||||
let m = manager.clone();
|
||||
handles.push(tokio::spawn(async move {
|
||||
let mut mgr = m.lock().await;
|
||||
let id = mgr.add_watcher().await;
|
||||
drop(mgr);
|
||||
tokio::time::sleep(Duration::from_millis(5)).await;
|
||||
let mut mgr = m.lock().await;
|
||||
let _ = mgr.remove_watcher(id).await;
|
||||
}));
|
||||
}
|
||||
for h in handles {
|
||||
h.await.unwrap();
|
||||
}
|
||||
let mgr = manager.lock().await;
|
||||
assert_eq!(mgr.watchers.lock().await.len(), 0);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
# tts_pipeline.py
|
||||
import asyncio
|
||||
import random
|
||||
import uuid
|
||||
from collections import deque
|
||||
|
||||
# ---------- mock synthesizer ----------
|
||||
_global_active = 0
|
||||
_global_max = 0
|
||||
_global_active_lock = asyncio.Lock()
|
||||
|
||||
async def mock_synthesize(text: str) -> bytes:
|
||||
global _global_active, _global_max
|
||||
async with _global_active_lock:
|
||||
_global_active += 1
|
||||
if _global_active > _global_max:
|
||||
_global_max = _global_active
|
||||
try:
|
||||
await asyncio.sleep(random.uniform(0.05, 0.30))
|
||||
if random.random() < 0.10:
|
||||
raise RuntimeError("synthesis failed")
|
||||
return b"\x00" * len(text)
|
||||
finally:
|
||||
async with _global_active_lock:
|
||||
_global_active -= 1
|
||||
|
||||
|
||||
# ---------- pipeline ----------
|
||||
class TTSPipeline:
|
||||
def __init__(self):
|
||||
self._queue = deque()
|
||||
self._queue_lock = asyncio.Lock()
|
||||
self._queue_not_empty = asyncio.Condition(self._queue_lock)
|
||||
|
||||
self._jobs_lock = asyncio.Lock()
|
||||
self._jobs = {} # id -> meta
|
||||
self._active_jobs = set()
|
||||
|
||||
self._callbacks = []
|
||||
self._workers = []
|
||||
|
||||
def register_callback(self, cb):
|
||||
self._callbacks.append(cb)
|
||||
|
||||
async def _emit(self, event, job_id, **kw):
|
||||
for cb in self._callbacks:
|
||||
try:
|
||||
cb(event, job_id, **kw)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def start(self):
|
||||
for _ in range(4):
|
||||
self._workers.append(asyncio.create_task(self._worker()))
|
||||
|
||||
async def _worker(self):
|
||||
while True:
|
||||
async with self._queue_lock:
|
||||
while not self._queue:
|
||||
await self._queue_not_empty.wait()
|
||||
job = self._queue.popleft()
|
||||
|
||||
job_id = job["id"]
|
||||
async with self._jobs_lock:
|
||||
meta = self._jobs.get(job_id)
|
||||
if meta and meta.get("cancelled"):
|
||||
await self._emit("cancelled", job_id)
|
||||
continue
|
||||
self._active_jobs.add(job_id)
|
||||
try:
|
||||
await self._process_job(job)
|
||||
finally:
|
||||
async with self._jobs_lock:
|
||||
self._active_jobs.discard(job_id)
|
||||
|
||||
async def _process_job(self, job):
|
||||
job_id = job["id"]
|
||||
async with self._jobs_lock:
|
||||
if job_id in self._jobs:
|
||||
self._jobs[job_id]["status"] = "started"
|
||||
await self._emit("started", job_id)
|
||||
|
||||
backoff = 0.1
|
||||
for attempt in range(1, 4):
|
||||
async with self._jobs_lock:
|
||||
meta = self._jobs.get(job_id)
|
||||
if not meta or meta.get("cancelled"):
|
||||
await self._emit("cancelled", job_id)
|
||||
return
|
||||
try:
|
||||
await mock_synthesize(job["text"])
|
||||
await self._emit("completed", job_id)
|
||||
async with self._jobs_lock:
|
||||
if job_id in self._jobs:
|
||||
self._jobs[job_id]["status"] = "completed"
|
||||
return
|
||||
except asyncio.CancelledError:
|
||||
await self._emit("cancelled", job_id)
|
||||
raise
|
||||
except Exception as e:
|
||||
async with self._jobs_lock:
|
||||
if job_id in self._jobs:
|
||||
self._jobs[job_id]["attempts"] = attempt
|
||||
if attempt >= 3:
|
||||
await self._emit("failed", job_id, error=e)
|
||||
async with self._jobs_lock:
|
||||
if job_id in self._jobs:
|
||||
self._jobs[job_id]["status"] = "failed"
|
||||
return
|
||||
await asyncio.sleep(backoff)
|
||||
backoff *= 2
|
||||
|
||||
async def submit(self, text: str, voice: str) -> str:
|
||||
job_id = uuid.uuid4().hex
|
||||
async with self._jobs_lock:
|
||||
self._jobs[job_id] = {
|
||||
"text": text, "voice": voice,
|
||||
"cancelled": False, "status": "queued", "attempts": 0
|
||||
}
|
||||
async with self._queue_lock:
|
||||
if len(self._queue) >= 100:
|
||||
async with self._jobs_lock:
|
||||
self._jobs.pop(job_id, None)
|
||||
raise RuntimeError("Backpressure: queue full")
|
||||
self._queue.append({"id": job_id, "text": text, "voice": voice})
|
||||
self._queue_not_empty.notify()
|
||||
await self._emit("queued", job_id)
|
||||
return job_id
|
||||
|
||||
async def cancel(self, job_id: str) -> bool:
|
||||
async with self._jobs_lock:
|
||||
meta = self._jobs.get(job_id)
|
||||
if not meta:
|
||||
return False
|
||||
if meta["status"] in ("completed", "failed", "cancelled"):
|
||||
return False
|
||||
meta["cancelled"] = True
|
||||
|
||||
removed = False
|
||||
async with self._queue_lock:
|
||||
for i, j in enumerate(self._queue):
|
||||
if j["id"] == job_id:
|
||||
del self._queue[i]
|
||||
removed = True
|
||||
break
|
||||
if removed:
|
||||
await self._emit("cancelled", job_id)
|
||||
return True
|
||||
|
||||
async def drain(self):
|
||||
while True:
|
||||
async with self._queue_lock:
|
||||
q_empty = len(self._queue) == 0
|
||||
async with self._jobs_lock:
|
||||
active_empty = len(self._active_jobs) == 0
|
||||
if q_empty and active_empty:
|
||||
break
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
|
||||
# ---------- tests ----------
|
||||
async def main():
|
||||
global _global_active, _global_max
|
||||
_global_active = 0
|
||||
_global_max = 0
|
||||
|
||||
# a) concurrency limit
|
||||
pipeline = TTSPipeline()
|
||||
await pipeline.start()
|
||||
ids = [await pipeline.submit(f"text {i}", "v1") for i in range(50)]
|
||||
await pipeline.drain()
|
||||
print("max concurrency", _global_max)
|
||||
assert _global_max <= 4, f"max concurrency {_global_max} > 4"
|
||||
|
||||
# b) backpressure
|
||||
_global_active = 0; _global_max = 0
|
||||
pipeline2 = TTSPipeline()
|
||||
await pipeline2.start()
|
||||
for i in range(100):
|
||||
await pipeline2.submit(f"t{i}", "v")
|
||||
try:
|
||||
await pipeline2.submit("overflow", "v")
|
||||
assert False, "should have raised"
|
||||
except RuntimeError as e:
|
||||
print("backpressure works:", e)
|
||||
await pipeline2.drain()
|
||||
|
||||
# c) cancel mid-flight and reuse
|
||||
_global_active = 0; _global_max = 0
|
||||
pipeline3 = TTSPipeline()
|
||||
await pipeline3.start()
|
||||
events3 = []
|
||||
pipeline3.register_callback(lambda e, jid, **kw: events3.append((e, jid)))
|
||||
ids3 = [await pipeline3.submit(f"t{i}", "v") for i in range(20)]
|
||||
await asyncio.sleep(0.05)
|
||||
cancel_id = ids3[5]
|
||||
await pipeline3.cancel(cancel_id)
|
||||
await pipeline3.drain()
|
||||
|
||||
queued = [e for e in events3 if e[0] == "queued"]
|
||||
completed = [e for e in events3 if e[0] == "completed"]
|
||||
failed = [e for e in events3 if e[0] == "failed"]
|
||||
cancelled = [e for e in events3 if e[0] == "cancelled"]
|
||||
print("queued", len(queued), "finished", len(completed)+len(failed)+len(cancelled))
|
||||
assert len(queued) == 20
|
||||
assert len(completed) + len(failed) + len(cancelled) == 20
|
||||
assert any(e[1] == cancel_id and e[0] == "cancelled" for e in events3)
|
||||
|
||||
# pool still usable
|
||||
new_id = await pipeline3.submit("after cancel", "v")
|
||||
await pipeline3.drain()
|
||||
print("pool still usable")
|
||||
print("All tests passed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,190 @@
|
||||
# webhook_bridge.py
|
||||
# Python 3.11+ stdlib only
|
||||
# Run: python webhook_bridge.py
|
||||
|
||||
import os, sys, json, hmac, hashlib, time, threading, queue, asyncio, random
|
||||
import http.server, http.client
|
||||
|
||||
SECRET = os.getenv('WEBHOOK_SECRET', 'supersecret')
|
||||
|
||||
# ---------- Idempotency ----------
|
||||
event_store = {}
|
||||
store_lock = threading.Lock()
|
||||
|
||||
def evict_stale():
|
||||
now = time.monotonic()
|
||||
with store_lock:
|
||||
stale = [k for k, v in event_store.items() if now - v > 300]
|
||||
for k in stale:
|
||||
del event_store[k]
|
||||
|
||||
# ---------- Rate limiter: token bucket 5/s ----------
|
||||
CAPACITY = 5
|
||||
RATE = 5.0
|
||||
tokens = CAPACITY
|
||||
last_refill = time.monotonic()
|
||||
bucket_lock = threading.Lock()
|
||||
|
||||
def acquire_token():
|
||||
global tokens, last_refill
|
||||
while True:
|
||||
with bucket_lock:
|
||||
now = time.monotonic()
|
||||
elapsed = now - last_refill
|
||||
tokens = min(CAPACITY, tokens + elapsed * RATE)
|
||||
last_refill = now
|
||||
if tokens >= 1:
|
||||
tokens -= 1
|
||||
return
|
||||
wait = (1 - tokens) / RATE
|
||||
time.sleep(wait)
|
||||
|
||||
# ---------- Mock Discord ----------
|
||||
async def discord_send(payload):
|
||||
await asyncio.sleep(0.005)
|
||||
if random.random() < 0.05: # ~5% 429
|
||||
class Resp:
|
||||
status = 429
|
||||
headers = {'Retry-After': '1'}
|
||||
return Resp()
|
||||
class Resp:
|
||||
status = 200
|
||||
headers = {}
|
||||
return Resp()
|
||||
|
||||
# ---------- Forwarding worker ----------
|
||||
forward_queue = queue.Queue()
|
||||
forward_log = [] # {event_id, ts}
|
||||
log_lock = threading.Lock()
|
||||
|
||||
def worker():
|
||||
while True:
|
||||
item = forward_queue.get()
|
||||
if item is None:
|
||||
break
|
||||
event_id, payload = item
|
||||
try:
|
||||
acquire_token()
|
||||
resp = asyncio.run(discord_send(payload))
|
||||
if resp.status == 429:
|
||||
retry = int(resp.headers.get('Retry-After', '1'))
|
||||
print(f"[WARN] 429 for {event_id}, retry after {retry}s", file=sys.stderr)
|
||||
time.sleep(retry)
|
||||
resp = asyncio.run(discord_send(payload))
|
||||
if resp.status == 200:
|
||||
with log_lock:
|
||||
forward_log.append({'event_id': event_id, 'ts': time.monotonic()})
|
||||
else:
|
||||
print(f"[ERROR] Discord forward failed {event_id} status {resp.status}", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Discord forward exception {e}", file=sys.stderr)
|
||||
finally:
|
||||
forward_queue.task_done()
|
||||
|
||||
# ---------- HTTP handler ----------
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
if self.path != '/webhook':
|
||||
self.send_response(404); self.end_headers(); return
|
||||
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(length)
|
||||
|
||||
sig = self.headers.get('X-Signature')
|
||||
if not sig:
|
||||
self.send_response(401); self.end_headers(); self.wfile.write(b'Missing signature'); return
|
||||
|
||||
expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
|
||||
if not hmac.compare_digest(expected, sig):
|
||||
self.send_response(401); self.end_headers(); self.wfile.write(b'Invalid signature'); return
|
||||
|
||||
try:
|
||||
data = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
self.send_response(400); self.end_headers(); self.wfile.write(b'Invalid JSON'); return
|
||||
|
||||
event_id = data.get('event_id')
|
||||
event_type = data.get('type')
|
||||
if not event_id or not event_type:
|
||||
self.send_response(400); self.end_headers(); self.wfile.write(b'Missing fields'); return
|
||||
|
||||
now = time.monotonic()
|
||||
with store_lock:
|
||||
evict_stale()
|
||||
if event_id in event_store and now - event_store[event_id] < 300:
|
||||
self.send_response(200); self.end_headers(); self.wfile.write(b'OK replay'); return
|
||||
event_store[event_id] = now
|
||||
|
||||
payload = {'content': f'Event {event_type} received', 'event_id': event_id}
|
||||
forward_queue.put((event_id, payload))
|
||||
self.send_response(200); self.end_headers(); self.wfile.write(b'OK')
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
print(f"{self.client_address[0]} - {fmt%args}")
|
||||
|
||||
# ---------- Tests ----------
|
||||
def make_request(event_id, event_type, secret=SECRET, tamper=False):
|
||||
body = json.dumps({'event_id': event_id, 'type': event_type, 'data': {}}).encode()
|
||||
sig = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
|
||||
if tamper:
|
||||
sig = '0'*64
|
||||
conn = http.client.HTTPConnection('localhost', 8000, timeout=5)
|
||||
conn.request('POST', '/webhook', body, {'Content-Type':'application/json','X-Signature':sig})
|
||||
resp = conn.getresponse()
|
||||
resp.read(); conn.close()
|
||||
return resp.status
|
||||
|
||||
def reset_state():
|
||||
with store_lock:
|
||||
event_store.clear()
|
||||
with log_lock:
|
||||
forward_log.clear()
|
||||
|
||||
def run_tests():
|
||||
time.sleep(0.2)
|
||||
print('Test a) correct signature')
|
||||
reset_state()
|
||||
s = make_request('id1','chat')
|
||||
time.sleep(0.1)
|
||||
assert s == 200, f'expected 200 got {s}'
|
||||
assert len(forward_log) == 1, 'should be forwarded once'
|
||||
print(' ok')
|
||||
|
||||
print('Test b) tampered signature')
|
||||
s = make_request('id2','chat', tamper=True)
|
||||
assert s == 401, f'expected 401 got {s}'
|
||||
assert len(forward_log) == 1, 'should not forward'
|
||||
print(' ok')
|
||||
|
||||
print('Test c) replay idempotency')
|
||||
s = make_request('id1','chat')
|
||||
assert s == 200
|
||||
time.sleep(0.05)
|
||||
assert len(forward_log) == 1, 'replay must not increase count'
|
||||
print(' ok')
|
||||
|
||||
print('Test d) rate limiter burst')
|
||||
reset_state()
|
||||
for i in range(10):
|
||||
make_request(f'burst{i}','chat')
|
||||
# wait for worker to drain
|
||||
time.sleep(2.5)
|
||||
assert len(forward_log) == 10, 'all 10 should eventually forward'
|
||||
ts = [e['ts'] for e in forward_log]
|
||||
elapsed = ts[-1] - ts[0]
|
||||
assert elapsed >= 1.5, f'rate limiter not enforced, elapsed {elapsed:.2f}s'
|
||||
print(f' ok, elapsed {elapsed:.2f}s >=1.5s')
|
||||
print('All tests passed.')
|
||||
|
||||
if __name__ == '__main__':
|
||||
worker_thread = threading.Thread(target=worker, daemon=True)
|
||||
worker_thread.start()
|
||||
|
||||
server = http.server.ThreadingHTTPServer(('localhost', 8000), Handler)
|
||||
srv_thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
srv_thread.start()
|
||||
|
||||
try:
|
||||
run_tests()
|
||||
finally:
|
||||
server.shutdown()
|
||||
Reference in New Issue
Block a user