专题研修

journal.py

专题研修约 1 千字

操作日志与真实崩溃恢复。对应第08、20、21章。依赖:标准库/SQLite。

单写者局部恢复;不是防恶意竞态的完整安全沙箱。阅读运行说明 / 查看实测结果

原始Python文件 · 64行

"""单写者本地文件CAS与SQLite操作日志。用于解释恢复边界,不是安全沙箱。不提供分布式exactly-once保证,不防有权限的恶意进程竞态。"""from __future__ import annotationsfrom pathlib import Pathimport hashlib,json,os,sqlite3,tempfile  def digest(text: str):return hashlib.sha256(text.encode('utf-8')).hexdigest()  class Journal:    def __init__(self,root: Path,db_path: Path):        self.root=root.resolve();self.db=sqlite3.connect(db_path,timeout=5)        self.db.execute('''CREATE TABLE IF NOT EXISTS operations(            task TEXT, op TEXT, path TEXT, before_hash TEXT, after_hash TEXT,            status TEXT, PRIMARY KEY(task,op))''')        self.db.commit()     def path(self,relative: str):        p=Path(relative)        if p.is_absolute() or '..' in p.parts:raise PermissionError('只允许工作区相对路径')        candidate=self.root/p        for part in [candidate,*list(candidate.parents)]:            if part==self.root:break            if part.is_symlink():raise PermissionError('教学实现拒绝符号链接路径')        resolved=candidate.resolve()        if not resolved.is_relative_to(self.root):raise PermissionError('工作区越界')        return resolved     def replace(self,task: str,op: str,relative: str,expected: str,new: str,*,crash=False):        if not task or not op:raise ValueError('任务与操作ID不能为空')        target=self.path(relative);bh,ah=digest(expected),digest(new)        row=self.db.execute('SELECT path,before_hash,after_hash,status FROM operations WHERE task=? AND op=?',(task,op)).fetchone()        current=target.read_text(encoding='utf-8');ch=digest(current)        if row:            if tuple(row[:3])!=(relative,bh,ah):raise ValueError('幂等ID复用为不同动作')            if ch==ah:                with self.db:self.db.execute("UPDATE operations SET status='reconciled' WHERE task=? AND op=?",(task,op))                return {'status':'reconciled','wrote':False,'version':ah}            if row[3] in ('applied','reconciled'):                raise RuntimeError('操作曾完成,但文件后来变化;不能重写覆盖')            if ch!=bh:raise RuntimeError('中断后状态不确定,需要人工或领域核对')        else:            if ch!=bh:raise RuntimeError('CAS前置版本已过期')            with self.db:self.db.execute('INSERT INTO operations VALUES(?,?,?,?,?,?)',(task,op,relative,bh,ah,'started'))        fd,name=tempfile.mkstemp(prefix='.edit-',dir=target.parent)        try:            with os.fdopen(fd,'w',encoding='utf-8') as f:f.write(new);f.flush();os.fsync(f.fileno())            os.replace(name,target)            if crash:os._exit(75)  # 故意真实终止子进程:文件已替换,但日志还没有applied。            with self.db:self.db.execute("UPDATE operations SET status='applied' WHERE task=? AND op=?",(task,op))            return {'status':'applied','wrote':True,'version':ah}        finally:            if os.path.exists(name):os.unlink(name)    def close(self):self.db.close()  if __name__=='__main__':    import argparse    p=argparse.ArgumentParser();p.add_argument('root',type=Path);p.add_argument('db',type=Path);p.add_argument('--crash',action='store_true');a=p.parse_args()    j=Journal(a.root,a.db)    try:print(json.dumps(j.replace('demo','op1','state.txt','old','new',crash=a.crash)))    finally:j.close()

输入关键词即可检索。

↑ ↓ 选择 Enter 打开Esc 关闭 · 仅本地搜索

阅读设置

阅读设置仅保存在本机浏览器,不上传任何数据。