mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-13 23:39:21 +02:00
Keep owned stdin valid until command completion on Boat
Boat invalidates descriptors for unlinked backing files, causing Node startup to abort. Retain the0600 stdin spool until normal cleanup, and reap abandoned command spools only after exact-owner retirement stops its cgroup. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
af93abd90a
commit
a9882c9dee
3 files changed
+18
-3
No files matched your search
@@ -196,7 +196,7 @@ export function boatBackend(
|
||||
return value;
|
||||
}
|
||||
const processProgram = String.raw`
|
||||
import os,sys,json,fcntl,subprocess
|
||||
import os,sys,json,fcntl,subprocess,shutil
|
||||
p=json.load(sys.stdin);base='/home/user/.paperclip-owners';os.makedirs(base,exist_ok=True)
|
||||
owner=p['owner'];oid=owner['id'];root=os.path.join(base,oid);os.makedirs(root,exist_ok=True)
|
||||
with open(os.path.join(root,'lock'),'a') as lock:
|
||||
@@ -215,6 +215,12 @@ with open(os.path.join(root,'lock'),'a') as lock:
|
||||
subprocess.run(['systemctl','--user','stop',slice,unit],capture_output=True)
|
||||
state=subprocess.run(['systemctl','--user','show',slice,'--property=ActiveState','--value'],capture_output=True,text=True).stdout.strip()
|
||||
if state not in ('inactive','failed',''):raise RuntimeError('process retirement unconfirmed')
|
||||
# Reap only this retired owner's command spools after its cgroup is stopped.
|
||||
# This covers controller crashes and SSH timeouts that skipped caller cleanup.
|
||||
for entry in os.scandir(root):
|
||||
if entry.name.startswith('command-') and entry.is_dir(follow_symlinks=False):
|
||||
try:shutil.rmtree(entry.path)
|
||||
except FileNotFoundError:pass
|
||||
print('{}')
|
||||
elif p['action']=='inspect':
|
||||
claim=owner.get('process');marker=os.path.join(root,'claim.json');actual=json.load(open(marker)) if os.path.exists(marker) else None
|
||||
|
||||
@@ -490,6 +490,14 @@ with open(output,'wb') as out,open(error,'wb') as err:
|
||||
cwd: temp,
|
||||
stdin: "foreground-input\n",
|
||||
});
|
||||
const stdinProbe = await binding.runner.execute({
|
||||
command: "python3",
|
||||
args: ["-c", "import os,sys; assert os.fstat(0).st_nlink > 0, 'stdin backing file was unlinked before child exit'; assert os.fstat(0).st_mode & 0o777 == 0o600; print(sys.stdin.read(),end='')"],
|
||||
cwd: temp,
|
||||
stdin: "persistent-stdin",
|
||||
});
|
||||
expect(stdinProbe.exitCode).toBe(0);
|
||||
expect(stdinProbe.stdout).toBe("persistent-stdin");
|
||||
backgroundPid = Number(result.stdout.trim());
|
||||
expect(result.exitCode).toBe(7);
|
||||
expect(result.stderr).toBe("foreground-input\n");
|
||||
|
||||
@@ -209,7 +209,7 @@ export function createComputerService(
|
||||
"-c",
|
||||
String.raw`
|
||||
import os,sys,json,fcntl,subprocess,time,tempfile,shutil
|
||||
p=json.load(sys.stdin);root='/home/user/.paperclip-owners/'+p['owner'];os.makedirs(root,exist_ok=True)
|
||||
p=json.load(sys.stdin);os.umask(0o077);root='/home/user/.paperclip-owners/'+p['owner'];os.makedirs(root,exist_ok=True)
|
||||
# A receipt reports foreground completion independently from cgroup lifetime.
|
||||
# ExitType=cgroup keeps nohup/setsid descendants owned until owner retirement.
|
||||
supervisor="""
|
||||
@@ -220,7 +220,8 @@ os.unlink(payload)
|
||||
i=p['input']
|
||||
try:
|
||||
with open(p['stdin'],'rb') as source:
|
||||
os.unlink(p['stdin'])
|
||||
# Boat's persistent filesystem invalidates unlinked stdin descriptors.
|
||||
# Keep the private backing file until the command's normal finally cleanup.
|
||||
child=subprocess.Popen([i['command']]+i.get('args',[]),stdin=source)
|
||||
code=child.wait()
|
||||
except Exception as error:
|
||||
|
||||
Reference in new issue
Block a user