git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 03/20] raceproof_create_file(): new function

From
Michael Haggerty <mhagger@alum.mit.edu>
Date
Feb 16, 2016, 13:22 UTC
Message-ID
<338201e98a16f6c53ed1ee447de10c206f2acc33.1455626201.git.mhagger@alum.mit.edu>
In-Reply-To
<cover.1455626201.git.mhagger@alum.mit.edu>

Add a function that tries to create a file and any containing directories in a way that is robust against races with other processes that might be cleaning up empty directories at the same time.

The actual file creation is done by a callback function, which, if it fails, should set errno to EISDIR or ENOENT according to the convention of open(). raceproof_create_file() detects such failures, and respectively either tries to delete empty directories that might be in the way of the file or tries to create the containing directories. Then it retries the callback function.

This function is not yet used.
Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
The actual file creation is done by a callback function, so I think
this function is flexible enough to be applicable in other
circumstances where similar races might occur. Perhaps when creating
loose object files in the ODB?

I was thinking about moving this function, along with safe_create_leading_directories() and safe_create_leading_directories_const(), to a more general module like path.c. But it didn't seem worth the code churn.

 cache.h     | 16 ++++++++++++++
 sha1_file.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+)
diff --git a/cache.h b/cache.h
index 7d3f80c..6e53cc8 100644
--- a/cache.h
+++ b/cache.h
@@ -976,6 +976,22 @@ enum scld_error {
 enum scld_error safe_create_leading_directories(char *path);
 enum scld_error safe_create_leading_directories_const(const char *path);
 
+typedef int create_file_fn(const char *path, void *cb);
+
+/*
+ * Create a file at path using fn, creating leading directories if
+ * necessary. If fn fails with errno==ENOENT, then try to create the
+ * containing directory and call fn again. If fn fails with
+ * errno==EISDIR, then delete the directory that is in the way if it
+ * is empty and call fn again. Retry a few times in case we are racing
+ * with another process that is trying to clean up the directory
+ * that contains path.
+ *
+ * In any case, the return value of this function and the errno that
+ * it sets are those resulting from the last call of fn.
+ */
+int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
+
 int mkdir_in_gitdir(const char *path);
 extern char *expand_user_path(const char *path);
 const char *enter_repo(const char *path, int strict);
diff --git a/sha1_file.c b/sha1_file.c
index a1ac646..31dcfe8 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -177,6 +177,75 @@ enum scld_error safe_create_leading_directories_const(const char *path)
 	return result;
 }
 
+int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
+{
+	/*
+	 * The number of times we will try to remove empty directories
+	 * in the way of path. This is only 1 because if another
+	 * process is racily creating directories that conflict with
+	 * us, we don't want to fight against them.
+	 */
+	int remove_directories_remaining = 1;
+
+	/*
+	 * The number of times that we will try to create the
+	 * directories containing path. We are willing to attempt this
+	 * more than once, because another process could be trying to
+	 * clean up empty directories at the same time as we are
+	 * trying to create them.
+	 */
+	int create_directories_remaining = 3;
+
+	/* A scratch copy of path, filled lazily if we need it: */
+	struct strbuf path_copy = STRBUF_INIT;
+
+	int save_errno;
+	int ret;
+
+retry_fn:
+	ret = fn(path, cb);
+	save_errno = errno;
+	if (!ret)
+		goto out;
+
+	if (errno == EISDIR && remove_directories_remaining > 0) {
+		/*
+		 * A directory is in the way. Maybe it is empty; try
+		 * to remove it:
+		 */
+		if (!path_copy.len)
+			strbuf_addstr(&path_copy, path);
+
+		if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY)) {
+			remove_directories_remaining--;
+			goto retry_fn;
+		}
+	} else if (errno == ENOENT && create_directories_remaining > 0) {
+		/*
+		 * Maybe the containing directory didn't exist, or
+		 * maybe it was just deleted by a process that is
+		 * racing with us to clean up empty directories. Try
+		 * to create it:
+		 */
+		enum scld_error scld_result;
+
+		if (!path_copy.len)
+			strbuf_addstr(&path_copy, path);
+
+		do {
+			create_directories_remaining--;
+			scld_result = safe_create_leading_directories(path_copy.buf);
+			if (scld_result == SCLD_OK)
+				goto retry_fn;
+		} while (scld_result == SCLD_VANISHED && create_directories_remaining > 0);
+	}
+
+out:
+	strbuf_release(&path_copy);
+	errno = save_errno;
+	return ret;
+}
+
 static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
 {
 	int i;
-- 
2.7.0
Previous: Michael HaggertyNext: Junio C Hamano
Message 7 of 34 in “Delete directories left empty after ref deletion”
  1. 00/20 Delete directories left empty after ref deletionMichael Haggerty, Feb 16, 2016
  2. 01/20 safe_create_leading_directories_const(): preserve errnoMichael Haggerty, Feb 16, 2016
  3. Jeff KingFeb 16, 2016
  4. 02/20 safe_create_leading_directories(): set errno on SCLD_EXISTSMichael Haggerty, Feb 16, 2016
  5. Junio C HamanoFeb 17, 2016
  6. Michael HaggertyFeb 18, 2016
  7. 03/20 raceproof_create_file(): new functionMichael Haggerty, Feb 16, 2016
  8. Junio C HamanoFeb 17, 2016
  9. 04/20 lock_ref_sha1_basic(): use raceproof_create_file()Michael Haggerty, Feb 16, 2016
  10. Junio C HamanoFeb 17, 2016
  11. 05/20 rename_tmp_log(): use raceproof_create_file()Michael Haggerty, Feb 16, 2016
  12. Junio C HamanoFeb 17, 2016
  13. Michael HaggertyFeb 19, 2016
  14. Junio C HamanoFeb 19, 2016
  15. 06/20 rename_tmp_log(): improve error reportingMichael Haggerty, Feb 16, 2016
  16. Junio C HamanoFeb 18, 2016
  17. 07/20 log_ref_setup(): separate code for create vs non-createMichael Haggerty, Feb 16, 2016
  18. 08/20 log_ref_setup(): improve robustness against racesMichael Haggerty, Feb 16, 2016
  19. Junio C HamanoFeb 18, 2016
  20. 09/20 log_ref_setup(): pass the open file descriptor back to the callerMichael Haggerty, Feb 16, 2016
  21. Junio C HamanoFeb 18, 2016
  22. 10/20 log_ref_write_1(): don't depend on logfileMichael Haggerty, Feb 16, 2016
  23. 11/20 log_ref_setup(): manage the name of the reflog file internallyMichael Haggerty, Feb 16, 2016
  24. 12/20 log_ref_write_1(): inline functionMichael Haggerty, Feb 16, 2016
  25. Junio C HamanoFeb 18, 2016
  26. 13/20 try_remove_empty_parents(): rename parameter "name" -> "refname"Michael Haggerty, Feb 16, 2016
  27. 14/20 try_remove_empty_parents(): don't trash argument contentsMichael Haggerty, Feb 16, 2016
  28. 15/20 try_remove_empty_parents(): don't accommodate consecutive slashesMichael Haggerty, Feb 16, 2016
  29. 16/20 t5505: use "for-each-ref" to test for the non-existence of referencesMichael Haggerty, Feb 16, 2016
  30. 17/20 delete_ref_loose(): derive loose reference path from lockMichael Haggerty, Feb 16, 2016
  31. 18/20 delete_ref_loose(): inline functionMichael Haggerty, Feb 16, 2016
  32. 19/20 try_remove_empty_parents(): teach to remove parents of reflogs, tooMichael Haggerty, Feb 16, 2016
  33. 20/20 ref_transaction_commit(): clean up empty directoriesMichael Haggerty, Feb 16, 2016
  34. Junio C HamanoFeb 17, 2016

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.