11d5b26d9798079ec5adedc103216ce818e2da5e
max
  Thu Sep 10 05:19:27 2026 -0700
Widen the session data directory hash from 8 to 10 hex characters

sessionDirFromNames() named a session's durable data directory with 8 hex
characters of md5(sessionName).  32 bits was fine while a directory only had
to be unique among one user's sessions, but every anonymous "Share a link"
session belongs to the single reserved user "l", which makes it a birthday
problem across all of them: two unrelated sessions land in the same directory
more likely than not at around 77,000 anonymous sessions, and at 500,000 we
would expect about 29 such pairs.  Two sessions sharing a directory means
cleaning up one takes the other's custom track files with it.

sessionDirHashLen is now 10 (40 bits), which moves the even-odds point past a
million sessions.  The two fan-out levels snapshotSessionDir() added for user
"l" do not help here, since they are a prefix of the same hash: they spread
the entries over 65536 directories but leave the number of distinct leaf names
unchanged.

Both directory layouts change name as a result, so snapshotCleaner would have
walked past anything written earlier and orphaned its files.  Both dir-naming
functions grew a hashLen argument, sessionDirHashLenLegacy records the old
value, and snapshotCleanAnon() now tries the old spelling of both layouts as
well as the new one.

Existing sessions keep working either way: the cart stores the absolute path
of each durable file, so nothing looks a session's directory up by name except
the cleaner.

refs #10138

diff --git src/hg/inc/sessionData.h src/hg/inc/sessionData.h
index 15129352ccd..7efe5a03c24 100644
--- src/hg/inc/sessionData.h
+++ src/hg/inc/sessionData.h
@@ -1,25 +1,41 @@
 /* sessionData - functions for moving user data out of trash into permanent storage
  *
  * Copyright (C) 2019-2024 The Regents of the University of California
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #ifndef SESSIONDATA_H
 #define SESSIONDATA_H
 
 char *sessionDataSaveTrashFile(char *trashPath, char *sessionDir);
 /* If trashPath exists and is not already a soft-link to sessionDir, alloc and return a new path in
  * sessionDir; move trashPath to new path and soft-link from trashPath to new path.
  * If trashPath is already a soft-link, return the path that it links to.
  * Return NULL if trashPath does not exist (can happen with expired custom track files). */
 
+/* Number of hex characters of md5(encSessionName) used to name a session's data directory.
+ * This was 8 (32 bits) until 2026.  That was safe while a directory only had to be unique among
+ * one user's sessions, but anonymous share links all live under the single reserved user name
+ * "l", which turns it into a birthday problem across every anonymous session: two of them share
+ * a directory more likely than not at ~77,000 sessions, and cleaning one up would take the
+ * other's files with it.  10 hex characters is 40 bits, which pushes that past 1,000,000.
+ * Directories written before the change are named with sessionDirHashLenLegacy characters, so
+ * code that deletes a session's directory must try both lengths. */
+#define sessionDirHashLen 10
+#define sessionDirHashLenLegacy 8
+
 char *sessionDirFromNames(char *sessionDataDir, char *encUserName, char *encSessionName);
 /* Alloc and return the per-session data directory under sessionDataDir (hashed by user and session
  * name), or NULL if sessionDataDir is empty.  errAborts if sessionDataDir is not an absolute path. */
 
+char *sessionDirFromNamesHashLen(char *sessionDataDir, char *encUserName, char *encSessionName,
+                                 int hashLen);
+/* Like sessionDirFromNames but with the number of session-hash characters spelled out, so that
+ * cleanup code can also name a directory written before sessionDirHashLen was widened. */
+
 void sessionDataSaveSession(struct cart *cart, char *encUserName, char *encSessionName,
                             char *dbSuffix);
 /* If hg.conf specifies safe places to store files and/or tables that belong to user sessions,
  * then scan cart for trashDir files and/or customTrash tables, store them in safe locations,
  * and update cart to point to the new locations. */
 
 #endif // SESSIONDATA_H