tempfile: remove deactivated list entries
authorJeff King <peff@peff.net>
Tue, 5 Sep 2017 12:15:04 +0000 (08:15 -0400)
committerJunio C Hamano <gitster@pobox.com>
Wed, 6 Sep 2017 08:19:54 +0000 (17:19 +0900)
Once a "struct tempfile" is added to the global cleanup
list, it is never removed. This means that its storage must
remain valid for the lifetime of the program. For single-use
tempfiles and locks, this isn't a big deal: we just declare
the struct static. But for library code which may take
multiple simultaneous locks (like the ref code), they're
forced to allocate a struct on the heap and leak it.

This is mostly OK in practice. The size of the leak is
bounded by the number of refs, and most programs exit after
operating on a fixed number of refs (and allocate
simultaneous memory proportional to the number of ref
updates in the first place). But:

1. It isn't hard to imagine a real leak: a program which
runs for a long time taking a series of ref update
instructions and fulfilling them one by one. I don't
think we have such a program now, but it's certainly
plausible.

2. The leaked entries appear as false positives to
tools like valgrind.

Let's relax this rule by keeping only "active" tempfiles on
the list. We can do this easily by moving the list-add
operation from prepare_tempfile_object to activate_tempfile,
and adding a deletion in deactivate_tempfile.

Existing callers do not need to be updated immediately.
They'll continue to leak any tempfile objects they may have
allocated, but that's no different than the status quo. We
can clean them up individually.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
tempfile.c
tempfile.h
index 11bda824cf77d87eb6d4718465497098721db9d5..f82a5f367631babbabf9f831ddfc9eb6d1029c19 100644 (file)
@@ -42,8 +42,7 @@
  *     states in which this condition doesn't hold). Client code should
  *     *not* rely on the filename being empty in this state.
  *   - `fd` is -1 and `fp` is `NULL`
- *   - the object is left registered in the `tempfile_list`, and
- *     `on_list` is set.
+ *   - the object is removed from `tempfile_list` (but could be used again)
  *
  * A temporary file is owned by the process that created it. The
  * `tempfile` has an `owner` field that records the owner's PID. This
@@ -92,36 +91,30 @@ static void remove_tempfiles_on_signal(int signo)
        raise(signo);
 }
 
-/*
- * Initialize *tempfile if necessary and add it to tempfile_list.
- */
 static void prepare_tempfile_object(struct tempfile *tempfile)
 {
-       if (volatile_list_empty(&tempfile_list)) {
-               /* One-time initialization */
-               sigchain_push_common(remove_tempfiles_on_signal);
-               atexit(remove_tempfiles_on_exit);
-       }
-
-       if (is_tempfile_active(tempfile))
-               BUG("prepare_tempfile_object called for active object");
-       if (!tempfile->on_list) {
-               /* Initialize *tempfile and add it to tempfile_list: */
-               tempfile->fd = -1;
-               tempfile->fp = NULL;
-               tempfile->active = 0;
-               tempfile->owner = 0;
-               strbuf_init(&tempfile->filename, 0);
-               volatile_list_add(&tempfile->list, &tempfile_list);
-               tempfile->on_list = 1;
-       } else if (tempfile->filename.len) {
-               /* This shouldn't happen, but better safe than sorry. */
-               BUG("prepare_tempfile_object called for improperly-reset object");
-       }
+       tempfile->fd = -1;
+       tempfile->fp = NULL;
+       tempfile->active = 0;
+       tempfile->owner = 0;
+       INIT_LIST_HEAD(&tempfile->list);
+       strbuf_init(&tempfile->filename, 0);
 }
 
 static void activate_tempfile(struct tempfile *tempfile)
 {
+       static int initialized;
+
+       if (is_tempfile_active(tempfile))
+               BUG("activate_tempfile called for active object");
+
+       if (!initialized) {
+               sigchain_push_common(remove_tempfiles_on_signal);
+               atexit(remove_tempfiles_on_exit);
+               initialized = 1;
+       }
+
+       volatile_list_add(&tempfile->list, &tempfile_list);
        tempfile->owner = getpid();
        tempfile->active = 1;
 }
@@ -130,6 +123,7 @@ static void deactivate_tempfile(struct tempfile *tempfile)
 {
        tempfile->active = 0;
        strbuf_release(&tempfile->filename);
+       volatile_list_del(&tempfile->list);
 }
 
 /* Make sure errno contains a meaningful value on error */
index 2ee24f43802086db7426d41b1a177617692f7a67..e32b4df0924d2cc16373c538cd958b6a2806271c 100644 (file)
  *
  * The caller:
  *
- * * Allocates a `struct tempfile` either as a static variable or on
- *   the heap, initialized to zeros. Once you use the structure to
- *   call `create_tempfile()`, it belongs to the tempfile subsystem
- *   and its storage must remain valid throughout the life of the
- *   program (i.e. you cannot use an on-stack variable to hold this
- *   structure).
+ * * Allocates a `struct tempfile`. Once the structure is passed to
+ *   `create_tempfile()`, its storage must remain valid until
+ *   `delete_tempfile()` or `rename_tempfile()` is called on it.
  *
  * * Attempts to create a temporary file by calling
  *   `create_tempfile()`.
@@ -52,9 +49,8 @@
  *   temporary file by calling `close_tempfile_gently()`, and later call
  *   `delete_tempfile()` or `rename_tempfile()`.
  *
- * Even after the temporary file is renamed or deleted, the `tempfile`
- * object must not be freed or altered by the caller. However, it may
- * be reused; just pass it to another call of `create_tempfile()`.
+ * After the temporary file is renamed or deleted, the `tempfile`
+ * object may be reused or freed.
  *
  * If the program exits before `rename_tempfile()` or
  * `delete_tempfile()` is called, an `atexit(3)` handler will close
@@ -88,7 +84,6 @@ struct tempfile {
        volatile int fd;
        FILE *volatile fp;
        volatile pid_t owner;
-       char on_list;
        struct strbuf filename;
 };