Skip to content

Commit 2f92aef

Browse files
authored
Add race condition detection for uploading the PyManager index (#240)
1 parent c652cd3 commit 2f92aef

File tree

1 file changed

+48
-10
lines changed

1 file changed

+48
-10
lines changed

windows-release/merge-and-upload.py

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,19 @@ def _run(*args):
7373
print(out.encode("ascii", "replace").decode("ascii"))
7474
if p.returncode:
7575
raise RunError(p.returncode, out)
76+
return out
7677

7778

7879
def call_ssh(*args, allow_fail=True):
7980
if not UPLOAD_HOST or NO_UPLOAD or LOCAL_INDEX:
8081
print("Skipping", args, "because UPLOAD_HOST is missing")
81-
return
82+
return ""
8283
try:
83-
_run(*_std_args(PLINK), f"{UPLOAD_USER}@{UPLOAD_HOST}", *args)
84-
except RunError:
84+
return _run(*_std_args(PLINK), f"{UPLOAD_USER}@{UPLOAD_HOST}", *args)
85+
except RunError as ex:
8586
if not allow_fail:
8687
raise
88+
return ex.args[1]
8789

8890

8991
def upload_ssh(source, dest):
@@ -229,6 +231,13 @@ def install_sortkey(install):
229231
)
230232

231233

234+
def find_missing_from_index(url, installs):
235+
with urlopen(url) as r:
236+
x = {install_sortkey(i) for i in json.load(r)["versions"]}
237+
y = {install_sortkey(i) for i in installs} - x
238+
return [i for i in installs if install_sortkey(i) in y]
239+
240+
232241
UPLOADS = list(calculate_uploads())
233242

234243
if not UPLOADS:
@@ -241,8 +250,16 @@ def install_sortkey(install):
241250

242251
index = {"versions": []}
243252

253+
INDEX_MTIME = 0
254+
244255
if INDEX_FILE:
245256
INDEX_PATH = url2path(INDEX_URL)
257+
258+
try:
259+
INDEX_MTIME = int(call_ssh("stat", "-c", "%Y", INDEX_PATH) or "0")
260+
except ValueError:
261+
pass
262+
246263
try:
247264
if not LOCAL_INDEX:
248265
download_ssh(INDEX_PATH, INDEX_FILE)
@@ -257,6 +274,8 @@ def install_sortkey(install):
257274
except FileNotFoundError:
258275
pass
259276

277+
print(INDEX_PATH, "mtime =", INDEX_MTIME)
278+
260279

261280
new_installs = [trim_install(i) for i, *_ in UPLOADS]
262281
validate_new_installs(new_installs)
@@ -293,25 +312,44 @@ def install_sortkey(install):
293312
upload_ssh(sbom, sbom_dest)
294313

295314

296-
if not NO_UPLOAD:
297-
if INDEX_FILE:
298-
print("Uploading", INDEX_FILE, "to", INDEX_URL)
299-
upload_ssh(INDEX_FILE, INDEX_PATH)
315+
# Check that nobody else has published while we were uploading
316+
if INDEX_FILE and INDEX_MTIME:
317+
try:
318+
mtime = int(call_ssh("stat", "-c", "%Y", INDEX_PATH) or "0")
319+
except ValueError:
320+
mtime = 0
321+
if mtime > INDEX_MTIME:
322+
print("##[error]Lost a race with another publish step!")
323+
print("Expecting mtime", INDEX_MTIME, "but saw", mtime)
324+
sys.exit(1)
300325

326+
327+
if not NO_UPLOAD:
301328
if MANIFEST_FILE:
302329
print("Uploading", MANIFEST_FILE, "to", MANIFEST_URL)
303330
upload_ssh(MANIFEST_FILE, MANIFEST_PATH)
304331

332+
if INDEX_FILE:
333+
print("Uploading", INDEX_FILE, "to", INDEX_URL)
334+
upload_ssh(INDEX_FILE, INDEX_PATH)
335+
305336
print("Purging", len(UPLOADS), "uploaded files")
306337
parents = set()
307338
for i, *_ in UPLOADS:
308339
purge(i["url"])
309340
parents.add(i["url"].rpartition("/")[0] + "/")
310341
for i in parents:
311342
purge(i)
312-
if INDEX_URL:
313-
purge(INDEX_URL)
314-
purge(INDEX_URL.rpartition("/")[0] + "/")
315343
if MANIFEST_URL:
316344
purge(MANIFEST_URL)
317345
purge(MANIFEST_URL.rpartition("/")[0] + "/")
346+
if INDEX_URL:
347+
purge(INDEX_URL)
348+
purge(INDEX_URL.rpartition("/")[0] + "/")
349+
missing = find_missing_from_index(INDEX_URL, [i for i, *_ in UPLOADS])
350+
if missing:
351+
print("##[error]Lost a race with another publish step!")
352+
print("Index at", INDEX_URL, "does not contain installs:")
353+
for m in missing:
354+
print(m["id"], m["sort-version"])
355+
sys.exit(1)

0 commit comments

Comments
 (0)