Commit b61f9015 authored by Ell's avatar Ell

buffer: improve handling of dynamically-changing swap dir

The current swap-dir management code doesn't cope well with
programs that modify the swap dir after initialization, which is
what GIMP is now doing (see issue gimp#2224.)

Move all the swap-dir management code to a new gegl-buffer-swap.c
file, and have gegl-init.c and gegl-buffer-config.c configure the
swap through this file.

The new file provides two new public functions:
gegl_buffer_swap_create_file(), which returns a unique filename for
a swap file in the current swap dir, and
gegl_buffer_swap_remove_file(), which removes and deletes a
previously-created swap file.  All swap files created with
create_file(), which hasn't been explicitly removed with
remove_file(), are automatically deleted on shutdown.  This allows
swap files to be safely deleted, even if the swap dir has been
changed since their creation.

Deprecate gegl_tile_backend_unlink_swap() in favor of the new
functions.

Use gegl_buffer_swap_{create,remove}_file() in the swap backend.
parent 3ee6879e
......@@ -63,6 +63,7 @@ GEGL_introspectable_headers = \
buffer/gegl-buffer-iterator.h \
buffer/gegl-buffer-iterator2.h \
buffer/gegl-buffer-backend.h \
buffer/gegl-buffer-swap.h \
buffer/gegl-rectangle.h \
buffer/gegl-tile-backend.h \
buffer/gegl-tile-handler.h \
......
......@@ -40,6 +40,7 @@ libbuffer_la_SOURCES = \
gegl-rectangle.c \
gegl-buffer-load.c \
gegl-buffer-save.c \
gegl-buffer-swap.c \
gegl-sampler.c \
gegl-sampler-cubic.c \
gegl-sampler-linear.c \
......@@ -70,6 +71,8 @@ libbuffer_la_SOURCES = \
gegl-rectangle.h \
gegl-buffer-types.h \
gegl-buffer-formats.h \
gegl-buffer-swap.h \
gegl-buffer-swap-private.h \
gegl-sampler.h \
gegl-sampler-cubic.h \
gegl-sampler-linear.h \
......
/* This file is part of GEGL
*
* GEGL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* GEGL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __GEGL_BUFFER_SWAP_PRIVATE_H__
#define __GEGL_BUFFER_SWAP_PRIVATE_H__
#include <glib.h>
#include <glib-object.h>
G_BEGIN_DECLS
void gegl_buffer_swap_init (void);
void gegl_buffer_swap_cleanup (void);
G_END_DECLS
#endif
/* This file is part of GEGL
*
* GEGL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* GEGL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef G_OS_WIN32
#include <windows.h>
#include <process.h>
#define getpid() _getpid()
#else
#include <signal.h>
#endif
#include <glib/gstdio.h>
#include "gegl-buffer-config.h"
#include "gegl-buffer-swap.h"
#include "gegl-buffer-swap-private.h"
#define SWAP_PREFIX "gegl-swap-"
/* local function prototypes */
static void gegl_buffer_swap_notify_swap (GeglBufferConfig *config);
static void gegl_buffer_swap_clean_dir (void);
static gboolean gegl_buffer_swap_pid_is_running (gint pid);
/* local variables */
static GMutex swap_mutex;
static gchar *swap_dir;
static GHashTable *swap_files;
static guint swap_file_counter;
/* public functions */
void
gegl_buffer_swap_init (void)
{
swap_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_signal_connect (gegl_buffer_config (), "notify::swap",
G_CALLBACK (gegl_buffer_swap_notify_swap),
NULL);
gegl_buffer_swap_notify_swap (gegl_buffer_config ());
}
void
gegl_buffer_swap_cleanup (void)
{
GHashTableIter iter;
const gchar *path;
g_signal_handlers_disconnect_by_func (gegl_buffer_config (),
gegl_buffer_swap_notify_swap,
NULL);
g_mutex_lock (&swap_mutex);
g_hash_table_iter_init (&iter, swap_files);
while (g_hash_table_iter_next (&iter, (gpointer) &path, NULL))
g_unlink (path);
g_clear_pointer (&swap_files, g_hash_table_destroy);
g_clear_pointer (&swap_dir, g_free);
g_mutex_unlock (&swap_mutex);
}
gchar *
gegl_buffer_swap_create_file (const gchar *suffix)
{
gchar *basename;
gchar *path;
gboolean added;
if (! swap_dir)
return NULL;
g_mutex_lock (&swap_mutex);
if (! swap_dir)
{
g_mutex_unlock (&swap_mutex);
return NULL;
}
if (suffix)
{
basename = g_strdup_printf (SWAP_PREFIX "%d-%u-%s",
(gint) getpid (),
swap_file_counter++,
suffix);
}
else
{
basename = g_strdup_printf (SWAP_PREFIX "%d-%u",
(gint) getpid (),
swap_file_counter++);
}
path = g_build_filename (swap_dir, basename, NULL);
added = g_hash_table_add (swap_files, path);
g_mutex_unlock (&swap_mutex);
g_free (basename);
if (! added)
{
g_warning ("swap file collision '%s'", path);
g_free (path);
return NULL;
}
return g_strdup (path);
}
void
gegl_buffer_swap_remove_file (const gchar *path)
{
gboolean removed;
g_return_if_fail (path != NULL);
g_mutex_lock (&swap_mutex);
removed = g_hash_table_remove (swap_files, path);
g_mutex_unlock (&swap_mutex);
if (removed)
g_unlink (path);
else
g_warning ("attempt to remove unregistered swap file '%s'", path);
}
/* private functions */
static void
gegl_buffer_swap_notify_swap (GeglBufferConfig *config)
{
gchar *dir = config->swap;
if (dir)
{
dir = g_strstrip (g_strdup (dir));
/* Remove any trailing separator, unless the path is only made of a
* leading separator.
*/
while (strlen (dir) > strlen (G_DIR_SEPARATOR_S) &&
g_str_has_suffix (dir, G_DIR_SEPARATOR_S))
{
dir[strlen (dir) - strlen (G_DIR_SEPARATOR_S)] = '\0';
}
}
g_mutex_lock (&swap_mutex);
if (! g_strcmp0 (dir, swap_dir))
{
g_mutex_unlock (&swap_mutex);
g_free (dir);
return;
}
g_clear_pointer (&swap_dir, g_free);
if (dir &&
! g_file_test (dir, G_FILE_TEST_IS_DIR) &&
g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
{
g_mutex_unlock (&swap_mutex);
g_free (dir);
return;
}
swap_dir = dir;
gegl_buffer_swap_clean_dir ();
g_mutex_unlock (&swap_mutex);
return;
}
static void
gegl_buffer_swap_clean_dir (void)
{
GDir *dir;
if (! swap_dir)
return;
dir = g_dir_open (swap_dir, 0, NULL);
if (dir != NULL)
{
const gchar *basename;
while ((basename = g_dir_read_name (dir)) != NULL)
{
if (g_str_has_prefix (basename, SWAP_PREFIX))
{
gint pid = atoi (basename + strlen (SWAP_PREFIX));
if (! gegl_buffer_swap_pid_is_running (pid))
{
gchar *path = g_build_filename (swap_dir, basename, NULL);
g_unlink (path);
g_free (path);
}
}
}
g_dir_close (dir);
}
}
#ifdef G_OS_WIN32
static gboolean
gegl_buffer_swap_pid_is_running (gint pid)
{
HANDLE h;
DWORD exitcode = 0;
h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, pid);
GetExitCodeProcess (h, &exitcode);
CloseHandle (h);
return exitcode == STILL_ACTIVE;
}
#else
static gboolean
gegl_buffer_swap_pid_is_running (gint pid)
{
return kill (pid, 0) == 0;
}
#endif
/* This file is part of GEGL
*
* GEGL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* GEGL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __GEGL_BUFFER_SWAP_H__
#define __GEGL_BUFFER_SWAP_H__
#include <glib.h>
#include <glib-object.h>
G_BEGIN_DECLS
/**
* gegl_buffer_swap_create_file:
* @suffix: (nullable): a string to suffix the filename with, for
* identification purposes, or %NULL.
*
* Generates a unique filename in the GEGL swap directory, suitable for
* using as swap space. When the file is no longer needed, it may be
* removed with gegl_buffer_swap_remove_file(); otherwise, it will be
* removed when gegl_exit() is called.
*
* Returns: (type filename) (nullable): a string containing the full
* file path, or %NULL is the swap is disabled. The returned string
* should be freed with g_free() when no longer needed.
*/
gchar * gegl_buffer_swap_create_file (const gchar *suffix);
/**
* gegl_buffer_swap_remove_file:
* @path: (type filename): the swap file to remove, as returned by
* gegl_buffer_swap_create_file()
*
* Removes a swap file, generated using gegl_buffer_swap_create_file(),
* unlinking the file, if exists.
*/
void gegl_buffer_swap_remove_file (const gchar *path);
G_END_DECLS
#endif
......@@ -183,24 +183,21 @@ gegl_config_class_init (GeglConfigClass *klass)
"Tile width",
"default tile width for created buffers.",
0, G_MAXINT, 128,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TILE_HEIGHT,
g_param_spec_int ("tile-height",
"Tile height",
"default tile height for created buffers.",
0, G_MAXINT, 128,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TILE_CACHE_SIZE,
g_param_spec_uint64 ("tile-cache-size",
"Tile Cache size",
"size of tile cache in bytes",
0, G_MAXUINT64, 512 * 1024 * 1024,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CHUNK_SIZE,
g_param_spec_int ("chunk-size",
......@@ -223,8 +220,7 @@ gegl_config_class_init (GeglConfigClass *klass)
"Swap",
"where gegl stores it's swap files",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
G_PARAM_READWRITE));
_gegl_threads = g_get_num_processors ();
_gegl_threads = MIN (_gegl_threads, GEGL_MAX_THREADS);
......@@ -250,8 +246,7 @@ gegl_config_class_init (GeglConfigClass *klass)
"Queue size",
"Maximum size of a file backend's writer thread queue (in bytes)",
2, G_MAXINT, 50 * 1024 *1024,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_APPLICATION_LICENSE,
g_param_spec_string ("application-license",
......@@ -274,7 +269,8 @@ gegl_config_init (GeglConfig *self)
GeglBufferConfig *bconf = gegl_buffer_config ();
for (int i = 0; forward_props[i]; i++)
g_object_bind_property (bconf, forward_props[i],
self, forward_props[i], G_BINDING_BIDIRECTIONAL);
self, forward_props[i],
G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
}
#undef gegl_config_threads
......
......@@ -56,29 +56,6 @@ DllMain (HINSTANCE hinstDLL,
return TRUE;
}
static inline gboolean
pid_is_running (gint pid)
{
HANDLE h;
DWORD exitcode = 0;
h = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, pid);
GetExitCodeProcess (h, &exitcode);
CloseHandle (h);
return exitcode == STILL_ACTIVE;
}
#else
#include <sys/types.h>
#include <signal.h>
static inline gboolean
pid_is_running (gint pid)
{
return (kill (pid, 0) == 0);
}
#endif
......@@ -99,6 +76,7 @@ guint gegl_debug_flags = 0;
#include "operation/gegl-operation-handlers-private.h"
#include "buffer/gegl-buffer-private.h"
#include "buffer/gegl-buffer-iterator-private.h"
#include "buffer/gegl-buffer-swap-private.h"
#include "buffer/gegl-tile-backend-ram.h"
#include "buffer/gegl-tile-backend-file.h"
#include "gegl-config.h"
......@@ -121,58 +99,6 @@ static GeglModuleDB *module_db = NULL;
static glong global_time = 0;
static gboolean swap_init_done = FALSE;
static gchar *swap_dir = NULL;
static void
gegl_init_swap_dir (void)
{
gchar *swapdir = NULL;
if (config->swap)
{
if (g_ascii_strcasecmp (config->swap, "ram") == 0)
{
swapdir = NULL;
}
else
{
swapdir = g_strstrip (g_strdup (config->swap));
/* Remove any trailing separator, unless the path is only made of a leading separator. */
while (strlen (swapdir) > strlen (G_DIR_SEPARATOR_S) && g_str_has_suffix (swapdir, G_DIR_SEPARATOR_S))
swapdir[strlen (swapdir) - strlen (G_DIR_SEPARATOR_S)] = '\0';
}
}
if (swapdir &&
! g_file_test (swapdir, G_FILE_TEST_IS_DIR) &&
g_mkdir_with_parents (swapdir, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
{
g_clear_pointer (&swapdir, g_free);
}
g_object_set (config, "swap", swapdir, NULL);
swap_dir = swapdir;
swap_init_done = TRUE;
}
/* Return the swap directory, or NULL if swapping is disabled */
const gchar *
gegl_swap_dir (void)
{
if (!swap_init_done)
{
g_critical ("swap parsing not done");
gegl_init_swap_dir ();
}
return swap_dir;
}
static void load_module_path(gchar *path, GeglModuleDB *db);
static void
......@@ -334,19 +260,6 @@ gegl_get_option_group (void)
return group;
}
static void gegl_config_set_defaults (GeglConfig *config)
{
gchar *swapdir = g_build_filename (g_get_user_cache_dir(),
GEGL_LIBRARY,
"swap",
NULL);
g_object_set (config,
"swap", swapdir,
NULL);
g_free (swapdir);
}
static void gegl_config_parse_env (GeglConfig *config)
{
if (g_getenv ("GEGL_QUALITY"))
......@@ -415,10 +328,8 @@ static void gegl_config_parse_env (GeglConfig *config)
GeglConfig *gegl_config (void)
{
if (!config)
{
config = g_object_new (GEGL_TYPE_CONFIG, NULL);
gegl_config_set_defaults (config);
}
config = g_object_new (GEGL_TYPE_CONFIG, NULL);
return config;
}
......@@ -435,43 +346,6 @@ void gegl_reset_stats (void)
gegl_stats_reset (gegl_stats ());
}
static void swap_clean (void)
{
const gchar *swap_dir = gegl_swap_dir ();
GDir *dir;
if (! swap_dir)
return;
dir = g_dir_open (gegl_swap_dir (), 0, NULL);
if (dir != NULL)
{
GPatternSpec *pattern = g_pattern_spec_new ("*");
const gchar *name;
while ((name = g_dir_read_name (dir)) != NULL)
{
if (g_pattern_match_string (pattern, name))
{
gint readpid = atoi (name);
if (!pid_is_running (readpid))
{
gchar *fname = g_build_filename (gegl_swap_dir (),
name,
NULL);
g_unlink (fname);
g_free (fname);
}
}
}
g_pattern_spec_free (pattern);
g_dir_close (dir);
}
}
void gegl_temp_buffer_free (void);
void
......@@ -491,6 +365,7 @@ gegl_exit (void)
gegl_operation_handlers_cleanup ();
gegl_random_cleanup ();
gegl_parallel_cleanup ();
gegl_buffer_swap_cleanup ();
gegl_cl_cleanup ();
gegl_temp_buffer_free ();
......@@ -526,44 +401,10 @@ gegl_exit (void)
#endif
}
if (gegl_swap_dir ())
{
/* remove all files matching <$GEGL_SWAP>/GEGL-<pid>-*.swap */
guint pid = getpid ();
GDir *dir = g_dir_open (gegl_swap_dir (), 0, NULL);
gchar *glob = g_strdup_printf ("%i-*", pid);
GPatternSpec *pattern = g_pattern_spec_new (glob);
g_free (glob);
if (dir != NULL)
{
const gchar *name;
while ((name = g_dir_read_name (dir)) != NULL)
{
if (g_pattern_match_string (pattern, name))
{
gchar *fname = g_build_filename (gegl_swap_dir (),
name,
NULL);
g_unlink (fname);
g_free (fname);
}
}
g_dir_close (dir);
}
g_pattern_spec_free (pattern);
}
g_clear_object (&config);
global_time = 0;
}
static void swap_clean (void);
void
gegl_get_version (int *major,
int *minor,
......@@ -700,10 +541,9 @@ gegl_post_parse_hook (GOptionContext *context,
if (cmd_gegl_disable_opencl)
gegl_cl_hard_disable ();
gegl_init_swap_dir ();
GEGL_INSTRUMENT_START();
gegl_buffer_swap_init ();
gegl_parallel_init ();
gegl_operation_gtype_init ();
gegl_tile_cache_init ();
......@@ -720,8 +560,6 @@ gegl_post_parse_hook (GOptionContext *context,
gegl_instrument ("gegl", "gegl_init", gegl_ticks () - global_time);
swap_clean ();
g_signal_connect (G_OBJECT (config),
"notify::use-opencl",
G_CALLBACK (gegl_config_use_opencl_notify),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment