upgrade: adopt V8.21.3 (V8.13→8.21.3: P5 SFTP/S3, P6 multilingual, editor overhaul, enterprise hardening)
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.
New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.
compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).
Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+42
-5
@@ -1,7 +1,7 @@
|
||||
'''Durable SQLite build queue. A single job is at most running once per site.
|
||||
'''Durable build queue: SQLite single-host or PostgreSQL with DB-owned leases.
|
||||
|
||||
This implementation is deliberately single-host. For multi-host HA, replace
|
||||
SQLite with PostgreSQL and Redis (outbox+leases) before production scaling.
|
||||
Redis is an optional wake-up signal, not an authoritative queue. PostgreSQL
|
||||
lock/lease semantics require actual multi-worker acceptance on infrastructure.
|
||||
'''
|
||||
from __future__ import annotations
|
||||
import hashlib
|
||||
@@ -101,9 +101,15 @@ def recover_expired():
|
||||
if row['cancel_requested']:
|
||||
state='cancelled'
|
||||
else:
|
||||
state='retrying' if row['attempts']<row['max_attempts'] else 'failed'
|
||||
# Publishing is an external side effect: it may have completed remotely
|
||||
# even though the Worker lost its lease. Never automatically replay it.
|
||||
state=('failed' if row['kind'] in ('remote_deploy','remote_rollback')
|
||||
else ('retrying' if row['attempts']<row['max_attempts'] else 'failed'))
|
||||
message=('Release lease expired; reconcile remote release before manual retry'
|
||||
if row['kind'] in ('remote_deploy','remote_rollback') else
|
||||
'Worker lease expired; verify partial builds')
|
||||
c.execute('UPDATE build_jobs SET state=?,worker_id=NULL,lease_until=NULL,available_at=?,error=? WHERE id=?',
|
||||
(state,db.now(),'Worker lease expired; verify partial builds',row['id']))
|
||||
(state,db.now(),message,row['id']))
|
||||
return len(rows)
|
||||
|
||||
|
||||
@@ -140,3 +146,34 @@ def submit_migration(site_id,session_id,actor,key=None):
|
||||
from . import redis_signal
|
||||
redis_signal.notify()
|
||||
return {'id':job_id,'state':'queued','existing':False}
|
||||
|
||||
|
||||
def submit_delivery(site_id,kind,payload,actor,key=None):
|
||||
# One logical release per job; serialized with build jobs by Worker.
|
||||
if kind not in ('remote_deploy','remote_rollback'):
|
||||
raise JobConflict('Unknown delivery job kind')
|
||||
if key is not None and not (8<=len(key)<=128 and all(c.isalnum() or c in '_-:.' for c in key)):
|
||||
raise JobConflict('无效的幂等键')
|
||||
normalized=json.dumps(payload,sort_keys=True,separators=(',',':'))
|
||||
with db.connect() as c:
|
||||
c.execute('BEGIN IMMEDIATE')
|
||||
if key:
|
||||
row=c.execute('SELECT * FROM build_jobs WHERE site_id=? AND idempotency_key=?',(site_id,key)).fetchone()
|
||||
if row:
|
||||
if row['kind']!=kind or row['payload_json']!=normalized:raise JobConflict('幂等键存在冲突')
|
||||
return {'id':row['id'],'state':row['state'],'existing':True}
|
||||
row=c.execute("SELECT id,state FROM build_jobs WHERE site_id=? AND kind=? AND payload_json=? AND state IN ('queued','running','retrying') ORDER BY created_at LIMIT 1",(site_id,kind,normalized)).fetchone()
|
||||
if row:return {'id':row['id'],'state':row['state'],'existing':True}
|
||||
active="state IN ('queued','running','retrying')"
|
||||
if c.execute(f'SELECT COUNT(*) FROM build_jobs WHERE {active}').fetchone()[0]>=_limit('SITEFORGE_MAX_PENDING_JOBS',100):
|
||||
raise JobCapacityError('系统任务队列已满')
|
||||
if c.execute(f'SELECT COUNT(*) FROM build_jobs WHERE site_id=? AND {active}',(site_id,)).fetchone()[0]>=_limit('SITEFORGE_MAX_PENDING_JOBS_PER_SITE',5):
|
||||
raise JobCapacityError('网站任务队列已满')
|
||||
job_id=uuid.uuid4().hex[:16]
|
||||
c.execute('''INSERT INTO build_jobs(id,site_id,kind,state,created_by,created_at,payload_json,
|
||||
idempotency_key,max_attempts,timeout_seconds,available_at)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?)''',
|
||||
(job_id,site_id,kind,'queued',actor,db.now(),normalized,key,3,900,db.now()))
|
||||
from . import redis_signal
|
||||
redis_signal.notify()
|
||||
return {'id':job_id,'state':'queued','existing':False}
|
||||
|
||||
Reference in New Issue
Block a user