mirror of
https://github.com/ostreedev/ostree.git
synced 2025-03-19 22:50:35 +03:00
Merge pull request #2348 from dbnicholson/mt-locking
Improve multi-threaded locking
This commit is contained in:
commit
5523aee082
@ -173,9 +173,9 @@ endif # USE_GPGME
|
||||
symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym
|
||||
|
||||
# Uncomment this include when adding new development symbols.
|
||||
#if BUILDOPT_IS_DEVEL_BUILD
|
||||
#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
|
||||
#endif
|
||||
if BUILDOPT_IS_DEVEL_BUILD
|
||||
symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
|
||||
endif
|
||||
|
||||
# http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html
|
||||
wl_versionscript_arg = -Wl,--version-script=
|
||||
|
@ -31,7 +31,7 @@ AM_CPPFLAGS += -DDATADIR='"$(datadir)"' -DLIBEXECDIR='"$(libexecdir)"' \
|
||||
-DOSTREE_COMPILATION \
|
||||
-DG_LOG_DOMAIN=\"OSTree\" \
|
||||
-DOSTREE_GITREV='"$(OSTREE_GITREV)"' \
|
||||
-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40 '-DGLIB_VERSION_MAX_ALLOWED=G_ENCODE_VERSION(2,50)' \
|
||||
-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_44 '-DGLIB_VERSION_MAX_ALLOWED=G_ENCODE_VERSION(2,50)' \
|
||||
-DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_2_40 '-DSOUP_VERSION_MAX_ALLOWED=G_ENCODE_VERSION(2,48)'
|
||||
# For strict aliasing, see https://bugzilla.gnome.org/show_bug.cgi?id=791622
|
||||
AM_CFLAGS += -std=gnu99 -fno-strict-aliasing $(WARN_CFLAGS)
|
||||
|
@ -319,6 +319,12 @@ ostree_repo_get_min_free_space_bytes
|
||||
ostree_repo_get_config
|
||||
ostree_repo_get_dfd
|
||||
ostree_repo_get_default_repo_finders
|
||||
OstreeRepoLockType
|
||||
ostree_repo_lock_pop
|
||||
ostree_repo_lock_push
|
||||
OstreeRepoAutoLock
|
||||
ostree_repo_auto_lock_push
|
||||
ostree_repo_auto_lock_cleanup
|
||||
ostree_repo_hash
|
||||
ostree_repo_equal
|
||||
ostree_repo_copy_config
|
||||
|
@ -109,7 +109,7 @@ AM_PATH_GLIB_2_0(,,AC_MSG_ERROR([GLib not found]))
|
||||
dnl When bumping the gio-unix-2.0 dependency (or glib-2.0 in general),
|
||||
dnl remember to bump GLIB_VERSION_MIN_REQUIRED and
|
||||
dnl GLIB_VERSION_MAX_ALLOWED in Makefile.am
|
||||
GIO_DEPENDENCY="gio-unix-2.0 >= 2.40.0"
|
||||
GIO_DEPENDENCY="gio-unix-2.0 >= 2.44.0"
|
||||
PKG_CHECK_MODULES(OT_DEP_GIO_UNIX, $GIO_DEPENDENCY)
|
||||
|
||||
dnl 5.1.0 is an arbitrary version here
|
||||
|
@ -22,6 +22,14 @@
|
||||
- uncomment the include in Makefile-libostree.am
|
||||
*/
|
||||
|
||||
LIBOSTREE_2021.3 {
|
||||
global:
|
||||
ostree_repo_auto_lock_push;
|
||||
ostree_repo_auto_lock_cleanup;
|
||||
ostree_repo_lock_push;
|
||||
ostree_repo_lock_pop;
|
||||
} LIBOSTREE_2021.2;
|
||||
|
||||
/* Stub section for the stable release *after* this development one; don't
|
||||
* edit this other than to update the year. This is just a copy/paste
|
||||
* source. Replace $LASTSTABLE with the last stable version, and $NEWVERSION
|
||||
|
@ -1684,8 +1684,8 @@ ostree_repo_prepare_transaction (OstreeRepo *self,
|
||||
|
||||
memset (&self->txn.stats, 0, sizeof (OstreeRepoTransactionStats));
|
||||
|
||||
self->txn_locked = _ostree_repo_lock_push (self, OSTREE_REPO_LOCK_SHARED,
|
||||
cancellable, error);
|
||||
self->txn_locked = ostree_repo_lock_push (self, OSTREE_REPO_LOCK_SHARED,
|
||||
cancellable, error);
|
||||
if (!self->txn_locked)
|
||||
return FALSE;
|
||||
|
||||
@ -2341,7 +2341,7 @@ ostree_repo_commit_transaction (OstreeRepo *self,
|
||||
|
||||
if (self->txn_locked)
|
||||
{
|
||||
if (!_ostree_repo_lock_pop (self, cancellable, error))
|
||||
if (!ostree_repo_lock_pop (self, OSTREE_REPO_LOCK_SHARED, cancellable, error))
|
||||
return FALSE;
|
||||
self->txn_locked = FALSE;
|
||||
}
|
||||
@ -2399,7 +2399,7 @@ ostree_repo_abort_transaction (OstreeRepo *self,
|
||||
|
||||
if (self->txn_locked)
|
||||
{
|
||||
if (!_ostree_repo_lock_pop (self, cancellable, error))
|
||||
if (!ostree_repo_lock_pop (self, OSTREE_REPO_LOCK_SHARED, cancellable, error))
|
||||
return FALSE;
|
||||
self->txn_locked = FALSE;
|
||||
}
|
||||
|
@ -104,6 +104,13 @@ typedef struct {
|
||||
fsblkcnt_t max_blocks;
|
||||
} OstreeRepoTxn;
|
||||
|
||||
typedef struct {
|
||||
GMutex mutex; /* All other members should only be accessed with this held */
|
||||
int fd; /* The open file or flock file descriptor */
|
||||
guint shared; /* Number of shared locks curently held */
|
||||
guint exclusive; /* Number of exclusive locks currently held */
|
||||
} OstreeRepoLock;
|
||||
|
||||
typedef enum {
|
||||
_OSTREE_FEATURE_NO,
|
||||
_OSTREE_FEATURE_MAYBE,
|
||||
@ -159,6 +166,8 @@ struct OstreeRepo {
|
||||
GWeakRef sysroot; /* Weak to avoid a circular ref; see also `is_system` */
|
||||
char *remotes_config_dir;
|
||||
|
||||
OstreeRepoLock lock;
|
||||
|
||||
GMutex txn_lock;
|
||||
OstreeRepoTxn txn;
|
||||
gboolean txn_locked;
|
||||
@ -506,30 +515,6 @@ _ostree_repo_maybe_regenerate_summary (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
/* Locking APIs are currently private.
|
||||
* See https://github.com/ostreedev/ostree/pull/1555
|
||||
*/
|
||||
typedef enum {
|
||||
OSTREE_REPO_LOCK_SHARED,
|
||||
OSTREE_REPO_LOCK_EXCLUSIVE
|
||||
} OstreeRepoLockType;
|
||||
|
||||
gboolean _ostree_repo_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
gboolean _ostree_repo_lock_pop (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
typedef OstreeRepo OstreeRepoAutoLock;
|
||||
|
||||
OstreeRepoAutoLock * _ostree_repo_auto_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void _ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *lock);
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoAutoLock, _ostree_repo_auto_lock_cleanup)
|
||||
|
||||
gboolean _ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error);
|
||||
|
||||
|
@ -204,7 +204,7 @@ ostree_repo_prune_static_deltas (OstreeRepo *self, const char *commit,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(OstreeRepoAutoLock) lock =
|
||||
_ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
if (!lock)
|
||||
return FALSE;
|
||||
|
||||
@ -325,7 +325,7 @@ ostree_repo_traverse_reachable_refs (OstreeRepo *self,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(OstreeRepoAutoLock) lock =
|
||||
_ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_SHARED, cancellable, error);
|
||||
ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_SHARED, cancellable, error);
|
||||
if (!lock)
|
||||
return FALSE;
|
||||
|
||||
@ -400,7 +400,7 @@ ostree_repo_prune (OstreeRepo *self,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(OstreeRepoAutoLock) lock =
|
||||
_ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
if (!lock)
|
||||
return FALSE;
|
||||
|
||||
@ -486,7 +486,7 @@ ostree_repo_prune_from_reachable (OstreeRepo *self,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(OstreeRepoAutoLock) lock =
|
||||
_ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
if (!lock)
|
||||
return FALSE;
|
||||
|
||||
|
@ -1270,7 +1270,7 @@ ostree_repo_static_delta_reindex (OstreeRepo *repo,
|
||||
|
||||
/* Protect against parallel prune operation */
|
||||
g_autoptr(OstreeRepoAutoLock) lock =
|
||||
_ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_SHARED, cancellable, error);
|
||||
ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_SHARED, cancellable, error);
|
||||
if (!lock)
|
||||
return FALSE;
|
||||
|
||||
|
@ -172,98 +172,84 @@ G_DEFINE_TYPE (OstreeRepo, ostree_repo, G_TYPE_OBJECT)
|
||||
/* Repository locking
|
||||
*
|
||||
* To guard against objects being deleted (e.g., prune) while they're in
|
||||
* use by another operation is accessing them (e.g., commit), the
|
||||
* use by another operation that is accessing them (e.g., commit), the
|
||||
* repository must be locked by concurrent writers.
|
||||
*
|
||||
* The locking is implemented by maintaining a thread local table of
|
||||
* lock stacks per repository. This allows thread safe locking since
|
||||
* each thread maintains its own lock stack. See the OstreeRepoLock type
|
||||
* below.
|
||||
* The repository locking has several important features:
|
||||
*
|
||||
* The actual locking is done using either open file descriptor locks or
|
||||
* flock locks. This allows the locking to work with concurrent
|
||||
* processes. The lock file is held on the ".lock" file within the
|
||||
* repository.
|
||||
* * There are 2 states - shared and exclusive. Multiple users can hold
|
||||
* a shared lock concurrently while only one user can hold an
|
||||
* exclusive lock.
|
||||
*
|
||||
* * The lock can be taken recursively so long as each acquisition is paired
|
||||
* with a matching release. The recursion is also latched to the strongest
|
||||
* state. Once an exclusive lock has been taken, it will remain exclusive
|
||||
* until all exclusive locks have been released.
|
||||
*
|
||||
* * It is both multiprocess- and multithread-safe. Threads that share
|
||||
* an OstreeRepo use the lock cooperatively while processes and
|
||||
* threads using separate OstreeRepo structures will block when
|
||||
* acquiring incompatible lock states.
|
||||
*
|
||||
* The actual locking is implemented using either open file descriptor
|
||||
* locks or flock locks. This allows the locking to work with concurrent
|
||||
* processes or concurrent threads using a separate OstreeRepo. The lock
|
||||
* file is held on the ".lock" file within the repository.
|
||||
*
|
||||
* The intended usage is to take a shared lock when writing objects or
|
||||
* reading objects in critical sections. Exclusive locks are taken when
|
||||
* deleting objects.
|
||||
*
|
||||
* To allow fine grained locking within libostree, the lock is
|
||||
* maintained as a stack. The core APIs then push or pop from the stack.
|
||||
* When pushing or popping a lock state identical to the existing or
|
||||
* next state, the stack is simply updated. Only when upgrading or
|
||||
* downgrading the lock (changing to/from unlocked, pushing exclusive on
|
||||
* shared or popping exclusive to shared) are actual locking operations
|
||||
* performed.
|
||||
* To allow fine grained locking, the lock state is maintained in shared and
|
||||
* exclusive counters. Callers then push or pop lock types to increment or
|
||||
* decrement the counters. When pushing or popping a lock type identical to
|
||||
* the existing or next state, the lock state is simply updated. Only when
|
||||
* upgrading or downgrading the lock (changing to/from unlocked, pushing
|
||||
* exclusive on shared or popping exclusive to shared) are actual locking
|
||||
* operations performed.
|
||||
*/
|
||||
|
||||
static void
|
||||
free_repo_lock_table (gpointer data)
|
||||
{
|
||||
GHashTable *lock_table = data;
|
||||
|
||||
if (lock_table != NULL)
|
||||
{
|
||||
g_debug ("Free lock table");
|
||||
g_hash_table_destroy (lock_table);
|
||||
}
|
||||
}
|
||||
|
||||
static GPrivate repo_lock_table = G_PRIVATE_INIT (free_repo_lock_table);
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
GQueue stack;
|
||||
} OstreeRepoLock;
|
||||
|
||||
typedef struct {
|
||||
guint len;
|
||||
int state;
|
||||
const char *name;
|
||||
} OstreeRepoLockInfo;
|
||||
|
||||
static void
|
||||
repo_lock_info (OstreeRepoLock *lock, OstreeRepoLockInfo *out_info)
|
||||
static const char *
|
||||
lock_state_name (int state)
|
||||
{
|
||||
g_assert (lock != NULL);
|
||||
g_assert (out_info != NULL);
|
||||
|
||||
OstreeRepoLockInfo info;
|
||||
info.len = g_queue_get_length (&lock->stack);
|
||||
if (info.len == 0)
|
||||
switch (state)
|
||||
{
|
||||
info.state = LOCK_UN;
|
||||
info.name = "unlocked";
|
||||
case LOCK_EX:
|
||||
return "exclusive";
|
||||
case LOCK_SH:
|
||||
return "shared";
|
||||
case LOCK_UN:
|
||||
return "unlocked";
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
else
|
||||
{
|
||||
info.state = GPOINTER_TO_INT (g_queue_peek_head (&lock->stack));
|
||||
info.name = (info.state == LOCK_EX) ? "exclusive" : "shared";
|
||||
}
|
||||
|
||||
*out_info = info;
|
||||
}
|
||||
|
||||
static void
|
||||
free_repo_lock (gpointer data)
|
||||
repo_lock_info (OstreeRepo *self, GMutexLocker *locker,
|
||||
OstreeRepoLockInfo *out_info)
|
||||
{
|
||||
OstreeRepoLock *lock = data;
|
||||
g_assert (self != NULL);
|
||||
g_assert (locker != NULL);
|
||||
g_assert (out_info != NULL);
|
||||
|
||||
if (lock != NULL)
|
||||
{
|
||||
OstreeRepoLockInfo info;
|
||||
repo_lock_info (lock, &info);
|
||||
OstreeRepoLockInfo info;
|
||||
info.len = self->lock.shared + self->lock.exclusive;
|
||||
if (info.len == 0)
|
||||
info.state = LOCK_UN;
|
||||
else if (self->lock.exclusive > 0)
|
||||
info.state = LOCK_EX;
|
||||
else
|
||||
info.state = LOCK_SH;
|
||||
info.name = lock_state_name (info.state);
|
||||
|
||||
g_debug ("Free lock: state=%s, depth=%u", info.name, info.len);
|
||||
g_queue_clear (&lock->stack);
|
||||
if (lock->fd >= 0)
|
||||
{
|
||||
g_debug ("Closing repo lock file");
|
||||
(void) close (lock->fd);
|
||||
}
|
||||
g_free (lock);
|
||||
}
|
||||
*out_info = info;
|
||||
}
|
||||
|
||||
/* Wrapper to handle flock vs OFD locking based on GLnxLockFile */
|
||||
@ -339,125 +325,148 @@ push_repo_lock (OstreeRepo *self,
|
||||
GError **error)
|
||||
{
|
||||
int flags = (lock_type == OSTREE_REPO_LOCK_EXCLUSIVE) ? LOCK_EX : LOCK_SH;
|
||||
int next_state = flags;
|
||||
if (!blocking)
|
||||
flags |= LOCK_NB;
|
||||
|
||||
GHashTable *lock_table = g_private_get (&repo_lock_table);
|
||||
if (lock_table == NULL)
|
||||
{
|
||||
g_debug ("Creating repo lock table");
|
||||
lock_table = g_hash_table_new_full (NULL, NULL, NULL,
|
||||
(GDestroyNotify)free_repo_lock);
|
||||
g_private_set (&repo_lock_table, lock_table);
|
||||
}
|
||||
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->lock.mutex);
|
||||
|
||||
OstreeRepoLock *lock = g_hash_table_lookup (lock_table, self);
|
||||
if (lock == NULL)
|
||||
if (self->lock.fd == -1)
|
||||
{
|
||||
lock = g_new0 (OstreeRepoLock, 1);
|
||||
g_queue_init (&lock->stack);
|
||||
g_debug ("Opening repo lock file");
|
||||
lock->fd = TEMP_FAILURE_RETRY (openat (self->repo_dir_fd, ".lock",
|
||||
O_CREAT | O_RDWR | O_CLOEXEC,
|
||||
DEFAULT_REGFILE_MODE));
|
||||
if (lock->fd < 0)
|
||||
{
|
||||
free_repo_lock (lock);
|
||||
return glnx_throw_errno_prefix (error,
|
||||
"Opening lock file %s/.lock failed",
|
||||
gs_file_get_path_cached (self->repodir));
|
||||
}
|
||||
g_hash_table_insert (lock_table, self, lock);
|
||||
self->lock.fd = TEMP_FAILURE_RETRY (openat (self->repo_dir_fd, ".lock",
|
||||
O_CREAT | O_RDWR | O_CLOEXEC,
|
||||
DEFAULT_REGFILE_MODE));
|
||||
if (self->lock.fd < 0)
|
||||
return glnx_throw_errno_prefix (error,
|
||||
"Opening lock file %s/.lock failed",
|
||||
gs_file_get_path_cached (self->repodir));
|
||||
}
|
||||
|
||||
OstreeRepoLockInfo info;
|
||||
repo_lock_info (lock, &info);
|
||||
repo_lock_info (self, locker, &info);
|
||||
g_debug ("Push lock: state=%s, depth=%u", info.name, info.len);
|
||||
|
||||
if (info.state == LOCK_EX)
|
||||
guint *counter;
|
||||
if (next_state == LOCK_EX)
|
||||
counter = &(self->lock.exclusive);
|
||||
else
|
||||
counter = &(self->lock.shared);
|
||||
|
||||
/* Check for overflow */
|
||||
if (*counter == G_MAXUINT)
|
||||
g_error ("Repo lock %s counter would overflow", lock_state_name (next_state));
|
||||
|
||||
if (info.state == LOCK_EX || info.state == next_state)
|
||||
{
|
||||
g_debug ("Repo already locked exclusively, extending stack");
|
||||
g_queue_push_head (&lock->stack, GINT_TO_POINTER (LOCK_EX));
|
||||
g_debug ("Repo already locked %s, maintaining state", info.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
int next_state = (flags & LOCK_EX) ? LOCK_EX : LOCK_SH;
|
||||
const char *next_state_name = (flags & LOCK_EX) ? "exclusive" : "shared";
|
||||
/* We should never upgrade from exclusive to shared */
|
||||
g_assert (!(info.state == LOCK_EX && next_state == LOCK_SH));
|
||||
|
||||
const char *next_state_name = lock_state_name (next_state);
|
||||
g_debug ("Locking repo %s", next_state_name);
|
||||
if (!do_repo_lock (lock->fd, flags))
|
||||
if (!do_repo_lock (self->lock.fd, flags))
|
||||
return glnx_throw_errno_prefix (error, "Locking repo %s failed",
|
||||
next_state_name);
|
||||
|
||||
g_queue_push_head (&lock->stack, GINT_TO_POINTER (next_state));
|
||||
}
|
||||
|
||||
/* Update state */
|
||||
(*counter)++;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pop_repo_lock (OstreeRepo *self,
|
||||
gboolean blocking,
|
||||
GError **error)
|
||||
pop_repo_lock (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
gboolean blocking,
|
||||
GError **error)
|
||||
{
|
||||
int flags = blocking ? 0 : LOCK_NB;
|
||||
|
||||
GHashTable *lock_table = g_private_get (&repo_lock_table);
|
||||
g_return_val_if_fail (lock_table != NULL, FALSE);
|
||||
|
||||
OstreeRepoLock *lock = g_hash_table_lookup (lock_table, self);
|
||||
g_return_val_if_fail (lock != NULL, FALSE);
|
||||
g_return_val_if_fail (lock->fd != -1, FALSE);
|
||||
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->lock.mutex);
|
||||
if (self->lock.fd == -1)
|
||||
g_error ("Cannot pop repo never locked repo lock");
|
||||
|
||||
OstreeRepoLockInfo info;
|
||||
repo_lock_info (lock, &info);
|
||||
g_return_val_if_fail (info.len > 0, FALSE);
|
||||
|
||||
repo_lock_info (self, locker, &info);
|
||||
g_debug ("Pop lock: state=%s, depth=%u", info.name, info.len);
|
||||
if (info.len > 1)
|
||||
{
|
||||
int next_state = GPOINTER_TO_INT (g_queue_peek_nth (&lock->stack, 1));
|
||||
|
||||
/* Drop back to the previous lock state if it differs */
|
||||
if (next_state != info.state)
|
||||
{
|
||||
/* We should never drop from shared to exclusive */
|
||||
g_return_val_if_fail (next_state == LOCK_SH, FALSE);
|
||||
g_debug ("Returning lock state to shared");
|
||||
if (!do_repo_lock (lock->fd, next_state | flags))
|
||||
return glnx_throw_errno_prefix (error,
|
||||
"Setting repo lock to shared failed");
|
||||
}
|
||||
else
|
||||
g_debug ("Maintaining lock state as %s", info.name);
|
||||
if (info.len == 0 || info.state == LOCK_UN)
|
||||
g_error ("Cannot pop already unlocked repo lock");
|
||||
|
||||
int state_to_drop;
|
||||
guint *counter;
|
||||
if (lock_type == OSTREE_REPO_LOCK_EXCLUSIVE)
|
||||
{
|
||||
state_to_drop = LOCK_EX;
|
||||
counter = &(self->lock.exclusive);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Lock stack will be empty, unlock */
|
||||
g_debug ("Unlocking repo");
|
||||
if (!do_repo_unlock (lock->fd, flags))
|
||||
return glnx_throw_errno_prefix (error, "Unlocking repo failed");
|
||||
state_to_drop = LOCK_SH;
|
||||
counter = &(self->lock.shared);
|
||||
}
|
||||
|
||||
g_queue_pop_head (&lock->stack);
|
||||
/* Make sure caller specified a valid type to release */
|
||||
if (*counter == 0)
|
||||
g_error ("Repo %s lock pop requested, but none have been taken",
|
||||
lock_state_name (state_to_drop));
|
||||
|
||||
int next_state;
|
||||
if (info.len == 1)
|
||||
{
|
||||
/* Lock counters will be empty, unlock */
|
||||
next_state = LOCK_UN;
|
||||
}
|
||||
else if (state_to_drop == LOCK_EX)
|
||||
next_state = (self->lock.exclusive > 1) ? LOCK_EX : LOCK_SH;
|
||||
else
|
||||
next_state = (self->lock.exclusive > 0) ? LOCK_EX : LOCK_SH;
|
||||
|
||||
if (next_state == LOCK_UN)
|
||||
{
|
||||
g_debug ("Unlocking repo");
|
||||
if (!do_repo_unlock (self->lock.fd, flags))
|
||||
return glnx_throw_errno_prefix (error, "Unlocking repo failed");
|
||||
}
|
||||
else if (info.state == next_state)
|
||||
{
|
||||
g_debug ("Maintaining lock state as %s", info.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We should never drop from shared to exclusive */
|
||||
g_assert (next_state == LOCK_SH);
|
||||
g_debug ("Returning lock state to shared");
|
||||
if (!do_repo_lock (self->lock.fd, next_state | flags))
|
||||
return glnx_throw_errno_prefix (error,
|
||||
"Setting repo lock to shared failed");
|
||||
}
|
||||
|
||||
/* Update state */
|
||||
(*counter)--;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* ostree_repo_lock_push:
|
||||
* @self: a #OstreeRepo
|
||||
* @lock_type: the type of lock to acquire
|
||||
* @cancellable: a #GCancellable
|
||||
* @error: a #GError
|
||||
*
|
||||
* Takes a lock on the repository and adds it to the lock stack. If @lock_type
|
||||
* Takes a lock on the repository and adds it to the lock state. If @lock_type
|
||||
* is %OSTREE_REPO_LOCK_SHARED, a shared lock is taken. If @lock_type is
|
||||
* %OSTREE_REPO_LOCK_EXCLUSIVE, an exclusive lock is taken. The actual lock
|
||||
* state is only changed when locking a previously unlocked repository or
|
||||
* upgrading the lock from shared to exclusive. If the requested lock state is
|
||||
* upgrading the lock from shared to exclusive. If the requested lock type is
|
||||
* unchanged or would represent a downgrade (exclusive to shared), the lock
|
||||
* state is not changed and the stack is simply updated.
|
||||
* state is not changed.
|
||||
*
|
||||
* ostree_repo_lock_push() waits for the lock depending on the repository's
|
||||
* lock-timeout-secs configuration. When lock-timeout-secs is -1, a blocking lock is
|
||||
@ -470,12 +479,13 @@ pop_repo_lock (OstreeRepo *self,
|
||||
* %TRUE is returned.
|
||||
*
|
||||
* Returns: %TRUE on success, otherwise %FALSE with @error set
|
||||
* Since: 2021.3
|
||||
*/
|
||||
gboolean
|
||||
_ostree_repo_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
ostree_repo_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, FALSE);
|
||||
g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE);
|
||||
@ -538,16 +548,19 @@ _ostree_repo_lock_push (OstreeRepo *self,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* _ostree_repo_lock_pop:
|
||||
/**
|
||||
* ostree_repo_lock_pop:
|
||||
* @self: a #OstreeRepo
|
||||
* @lock_type: the type of lock to release
|
||||
* @cancellable: a #GCancellable
|
||||
* @error: a #GError
|
||||
*
|
||||
* Remove the current repository lock state from the lock stack. If the lock
|
||||
* stack becomes empty, the repository is unlocked. Otherwise, the lock state
|
||||
* only changes when transitioning from an exclusive lock back to a shared
|
||||
* lock.
|
||||
* Release a lock of type @lock_type from the lock state. If the lock state
|
||||
* becomes empty, the repository is unlocked. Otherwise, the lock state only
|
||||
* changes when transitioning from an exclusive lock back to a shared lock. The
|
||||
* requested @lock_type must be the same type that was requested in the call to
|
||||
* ostree_repo_lock_push(). It is a programmer error if these do not match and
|
||||
* the program may abort if the lock would reach an invalid state.
|
||||
*
|
||||
* ostree_repo_lock_pop() waits for the lock depending on the repository's
|
||||
* lock-timeout-secs configuration. When lock-timeout-secs is -1, a blocking lock is
|
||||
@ -560,11 +573,13 @@ _ostree_repo_lock_push (OstreeRepo *self,
|
||||
* %TRUE is returned.
|
||||
*
|
||||
* Returns: %TRUE on success, otherwise %FALSE with @error set
|
||||
* Since: 2021.3
|
||||
*/
|
||||
gboolean
|
||||
_ostree_repo_lock_pop (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
ostree_repo_lock_pop (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, FALSE);
|
||||
g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE);
|
||||
@ -581,7 +596,7 @@ _ostree_repo_lock_pop (OstreeRepo *self,
|
||||
else if (self->lock_timeout_seconds == REPO_LOCK_BLOCKING)
|
||||
{
|
||||
g_debug ("Popping lock blocking");
|
||||
return pop_repo_lock (self, TRUE, error);
|
||||
return pop_repo_lock (self, lock_type, TRUE, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -596,7 +611,7 @@ _ostree_repo_lock_pop (OstreeRepo *self,
|
||||
return FALSE;
|
||||
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
if (pop_repo_lock (self, FALSE, &local_error))
|
||||
if (pop_repo_lock (self, lock_type, FALSE, &local_error))
|
||||
return TRUE;
|
||||
|
||||
if (!g_error_matches (local_error, G_IO_ERROR,
|
||||
@ -627,60 +642,72 @@ _ostree_repo_lock_pop (OstreeRepo *self,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* _ostree_repo_auto_lock_push: (skip)
|
||||
struct OstreeRepoAutoLock {
|
||||
OstreeRepo *repo;
|
||||
OstreeRepoLockType lock_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* ostree_repo_auto_lock_push: (skip)
|
||||
* @self: a #OstreeRepo
|
||||
* @lock_type: the type of lock to acquire
|
||||
* @cancellable: a #GCancellable
|
||||
* @error: a #GError
|
||||
*
|
||||
* Like ostree_repo_lock_push(), but for usage with #OstreeRepoAutoLock.
|
||||
* The intended usage is to declare the #OstreeRepoAutoLock with
|
||||
* g_autoptr() so that ostree_repo_auto_lock_cleanup() is called when it
|
||||
* goes out of scope. This will automatically pop the lock status off
|
||||
* the stack if it was acquired successfully.
|
||||
* Like ostree_repo_lock_push(), but for usage with #OstreeRepoAutoLock. The
|
||||
* intended usage is to declare the #OstreeRepoAutoLock with g_autoptr() so
|
||||
* that ostree_repo_auto_lock_cleanup() is called when it goes out of scope.
|
||||
* This will automatically release the lock if it was acquired successfully.
|
||||
*
|
||||
* |[<!-- language="C" -->
|
||||
* g_autoptr(OstreeRepoAutoLock) lock = NULL;
|
||||
* lock = _ostree_repo_auto_lock_push (repo, lock_type, cancellable, error);
|
||||
* lock = ostree_repo_auto_lock_push (repo, lock_type, cancellable, error);
|
||||
* if (!lock)
|
||||
* return FALSE;
|
||||
* ]|
|
||||
*
|
||||
* Returns: @self on success, otherwise %NULL with @error set
|
||||
* Since: 2021.3
|
||||
*/
|
||||
OstreeRepoAutoLock *
|
||||
_ostree_repo_auto_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
ostree_repo_auto_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!_ostree_repo_lock_push (self, lock_type, cancellable, error))
|
||||
if (!ostree_repo_lock_push (self, lock_type, cancellable, error))
|
||||
return NULL;
|
||||
return (OstreeRepoAutoLock *)self;
|
||||
|
||||
OstreeRepoAutoLock *auto_lock = g_slice_new (OstreeRepoAutoLock);
|
||||
auto_lock->repo = self;
|
||||
auto_lock->lock_type = lock_type;
|
||||
return auto_lock;
|
||||
}
|
||||
|
||||
/*
|
||||
* _ostree_repo_auto_lock_cleanup: (skip)
|
||||
/**
|
||||
* ostree_repo_auto_lock_cleanup: (skip)
|
||||
* @lock: a #OstreeRepoAutoLock
|
||||
*
|
||||
* A cleanup handler for use with ostree_repo_auto_lock_push(). If @lock is
|
||||
* not %NULL, ostree_repo_lock_pop() will be called on it. If
|
||||
* ostree_repo_lock_pop() fails, a critical warning will be emitted.
|
||||
*
|
||||
* Since: 2021.3
|
||||
*/
|
||||
void
|
||||
_ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *lock)
|
||||
ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *auto_lock)
|
||||
{
|
||||
OstreeRepo *repo = lock;
|
||||
if (repo)
|
||||
if (auto_lock != NULL)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
int errsv = errno;
|
||||
|
||||
if (!_ostree_repo_lock_pop (repo, NULL, &error))
|
||||
if (!ostree_repo_lock_pop (auto_lock->repo, auto_lock->lock_type, NULL, &error))
|
||||
g_critical ("Cleanup repo lock failed: %s", error->message);
|
||||
|
||||
errno = errsv;
|
||||
|
||||
g_slice_free (OstreeRepoAutoLock, auto_lock);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1052,13 +1079,8 @@ ostree_repo_finalize (GObject *object)
|
||||
g_clear_pointer (&self->remotes, g_hash_table_destroy);
|
||||
g_mutex_clear (&self->remotes_lock);
|
||||
|
||||
GHashTable *lock_table = g_private_get (&repo_lock_table);
|
||||
if (lock_table)
|
||||
{
|
||||
g_hash_table_remove (lock_table, self);
|
||||
if (g_hash_table_size (lock_table) == 0)
|
||||
g_private_replace (&repo_lock_table, NULL);
|
||||
}
|
||||
glnx_close_fd (&self->lock.fd);
|
||||
g_mutex_clear (&self->lock.mutex);
|
||||
|
||||
G_OBJECT_CLASS (ostree_repo_parent_class)->finalize (object);
|
||||
}
|
||||
@ -1220,6 +1242,7 @@ ostree_repo_init (OstreeRepo *self)
|
||||
self->test_error_flags = g_parse_debug_string (g_getenv ("OSTREE_REPO_TEST_ERROR"),
|
||||
test_error_keys, G_N_ELEMENTS (test_error_keys));
|
||||
|
||||
g_mutex_init (&self->lock.mutex);
|
||||
g_mutex_init (&self->cache_lock);
|
||||
g_mutex_init (&self->txn_lock);
|
||||
|
||||
@ -1233,6 +1256,7 @@ ostree_repo_init (OstreeRepo *self)
|
||||
self->tmp_dir_fd = -1;
|
||||
self->objects_dir_fd = -1;
|
||||
self->uncompressed_objects_dir_fd = -1;
|
||||
self->lock.fd = -1;
|
||||
self->sysroot_kind = OSTREE_REPO_SYSROOT_KIND_UNKNOWN;
|
||||
}
|
||||
|
||||
@ -5791,8 +5815,8 @@ ostree_repo_regenerate_summary (OstreeRepo *self,
|
||||
g_autoptr(OstreeRepoAutoLock) lock = NULL;
|
||||
gboolean no_deltas_in_summary = FALSE;
|
||||
|
||||
lock = _ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE,
|
||||
cancellable, error);
|
||||
lock = ostree_repo_auto_lock_push (self, OSTREE_REPO_LOCK_EXCLUSIVE,
|
||||
cancellable, error);
|
||||
if (!lock)
|
||||
return FALSE;
|
||||
|
||||
|
@ -1498,6 +1498,57 @@ gboolean ostree_repo_regenerate_summary (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
|
||||
/**
|
||||
* OstreeRepoLockType:
|
||||
* @OSTREE_REPO_LOCK_SHARED: A "read only" lock; multiple readers are allowed.
|
||||
* @OSTREE_REPO_LOCK_EXCLUSIVE: A writable lock at most one writer can be active, and zero readers.
|
||||
*
|
||||
* Flags controlling repository locking.
|
||||
*
|
||||
* Since: 2021.3
|
||||
*/
|
||||
typedef enum {
|
||||
OSTREE_REPO_LOCK_SHARED,
|
||||
OSTREE_REPO_LOCK_EXCLUSIVE
|
||||
} OstreeRepoLockType;
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_repo_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_repo_lock_pop (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
/* C convenience API only */
|
||||
#ifndef __GI_SCANNER__
|
||||
|
||||
/**
|
||||
* OstreeRepoAutoLock: (skip)
|
||||
*
|
||||
* An opaque type for use with ostree_repo_auto_lock_push().
|
||||
*
|
||||
* Since: 2021.3
|
||||
*/
|
||||
typedef struct OstreeRepoAutoLock OstreeRepoAutoLock;
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
OstreeRepoAutoLock * ostree_repo_auto_lock_push (OstreeRepo *self,
|
||||
OstreeRepoLockType lock_type,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
void ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *lock);
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoAutoLock, ostree_repo_auto_lock_cleanup)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* OSTREE_REPO_METADATA_REF:
|
||||
*
|
||||
|
@ -505,7 +505,7 @@ ostree_sysroot_cleanup_prune_repo (OstreeSysroot *sysroot,
|
||||
* the prune.
|
||||
*/
|
||||
g_autoptr(OstreeRepoAutoLock) lock =
|
||||
_ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_EXCLUSIVE, cancellable, error);
|
||||
if (!lock)
|
||||
return FALSE;
|
||||
|
||||
|
@ -40,11 +40,15 @@ def mktree(dname, serial=0):
|
||||
f.write('{} {} {}\n'.format(dname, serial, v))
|
||||
|
||||
subprocess.check_call(['ostree', '--repo=repo', 'init', '--mode=bare'])
|
||||
# like the bit in libtest, but let's do it unconditionally since it's simpler,
|
||||
# and we don't need xattr coverage for this
|
||||
with open('repo/config', 'a') as f:
|
||||
# like the bit in libtest, but let's do it unconditionally since
|
||||
# it's simpler, and we don't need xattr coverage for this
|
||||
f.write('disable-xattrs=true\n')
|
||||
|
||||
# Make any locking errors fail quickly instead of blocking the test
|
||||
# for 30 seconds.
|
||||
f.write('lock-timeout-secs=5\n')
|
||||
|
||||
def commit(v):
|
||||
tdir='tree{}'.format(v)
|
||||
cmd = ['ostree', '--repo=repo', 'commit', '--fsync=0', '-b', tdir, '--tree=dir='+tdir]
|
||||
|
@ -134,4 +134,12 @@ w.write(inline_content.slice(10), null)
|
||||
let actual_checksum = w.finish(null)
|
||||
assertEquals(actual_checksum, networks_checksum)
|
||||
|
||||
// Basic locking API sanity test
|
||||
repo.lock_push(OSTree.RepoLockType.SHARED, null);
|
||||
repo.lock_push(OSTree.RepoLockType.SHARED, null);
|
||||
repo.lock_pop(OSTree.RepoLockType.SHARED, null);
|
||||
repo.lock_pop(OSTree.RepoLockType.SHARED, null);
|
||||
repo.lock_push(OSTree.RepoLockType.EXCLUSIVE, null);
|
||||
repo.lock_pop(OSTree.RepoLockType.EXCLUSIVE, null);
|
||||
|
||||
print("ok test-core");
|
||||
|
@ -51,6 +51,29 @@ setup (Fixture *fixture,
|
||||
g_test_message ("Using temporary directory: %s", fixture->tmpdir.path);
|
||||
}
|
||||
|
||||
/* Common setup for locking tests. Create an archive repo in the tmpdir and
|
||||
* set the locking timeout to 0 so lock failures don't block.
|
||||
*/
|
||||
static void
|
||||
lock_setup (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
setup (fixture, test_data);
|
||||
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(OstreeRepo) repo = ostree_repo_create_at (fixture->tmpdir.fd, ".",
|
||||
OSTREE_REPO_MODE_ARCHIVE,
|
||||
NULL,
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Set the lock timeout to 0 so failures don't block the test */
|
||||
g_autoptr(GKeyFile) config = ostree_repo_copy_config (repo);
|
||||
g_key_file_set_integer (config, "core", "lock-timeout-secs", 0);
|
||||
ostree_repo_write_config (repo, config, &error);
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
@ -249,6 +272,301 @@ test_write_regfile_api (Fixture *fixture,
|
||||
g_assert_cmpstr (checksum, ==, "23a2e97d21d960ac7a4e39a8721b1baff7b213e00e5e5641334f50506012fcff");
|
||||
}
|
||||
|
||||
/* Just a sanity check of the C autolocking API */
|
||||
static void
|
||||
test_repo_autolock (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(OstreeRepo) repo = ostree_repo_create_at (fixture->tmpdir.fd, ".",
|
||||
OSTREE_REPO_MODE_ARCHIVE,
|
||||
NULL,
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
{
|
||||
g_autoptr(OstreeRepoAutoLock) lock = ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
||||
g_autoptr(OstreeRepoAutoLock) lock1 = ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
g_autoptr(OstreeRepoAutoLock) lock2 = ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
||||
/* Locking from single thread with a single OstreeRepo */
|
||||
static void
|
||||
test_repo_lock_single (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(OstreeRepo) repo = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Single thread on a single repo can freely recurse in any state */
|
||||
ostree_repo_lock_push (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_push (repo, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_push (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
||||
/* Unlocking without having ever locked */
|
||||
static void
|
||||
test_repo_lock_unlock_never_locked (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
if (g_test_subprocess ())
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(OstreeRepo) repo = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
ostree_repo_lock_pop (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
g_test_trap_subprocess (NULL, 0, 0);
|
||||
g_test_trap_assert_failed ();
|
||||
g_test_trap_assert_stderr ("*ERROR*Cannot pop repo never locked repo lock\n");
|
||||
}
|
||||
|
||||
/* Unlocking after already unlocked */
|
||||
static void
|
||||
test_repo_lock_double_unlock (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
if (g_test_subprocess ())
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(OstreeRepo) repo = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
ostree_repo_lock_push (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
g_test_trap_subprocess (NULL, 0, 0);
|
||||
g_test_trap_assert_failed ();
|
||||
g_test_trap_assert_stderr ("*ERROR*Cannot pop already unlocked repo lock\n");
|
||||
}
|
||||
|
||||
/* Unlocking the wrong type */
|
||||
static void
|
||||
test_repo_lock_unlock_wrong_type (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
if (g_test_subprocess ())
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(OstreeRepo) repo = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
ostree_repo_lock_push (repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
g_test_trap_subprocess (NULL, 0, 0);
|
||||
g_test_trap_assert_failed ();
|
||||
g_test_trap_assert_stderr ("*ERROR*Repo exclusive lock pop requested, but none have been taken\n");
|
||||
}
|
||||
|
||||
/* Locking with single thread and multiple OstreeRepos */
|
||||
static void
|
||||
test_repo_lock_multi_repo (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
/* Open two OstreeRepo instances */
|
||||
g_autoptr(OstreeRepo) repo1 = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_autoptr(OstreeRepo) repo2 = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Single thread with multiple OstreeRepo's conflict */
|
||||
ostree_repo_lock_push (repo1, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_push (repo2, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_push (repo1, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
|
||||
g_clear_error (&error);
|
||||
ostree_repo_lock_pop (repo1, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo2, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Recursive lock should stay exclusive once acquired */
|
||||
ostree_repo_lock_push (repo1, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_push (repo1, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_push (repo2, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
|
||||
g_clear_error (&error);
|
||||
ostree_repo_lock_push (repo2, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
|
||||
g_clear_error (&error);
|
||||
ostree_repo_lock_pop (repo1, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
ostree_repo_lock_pop (repo1, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
||||
/* Locking from multiple threads with a single OstreeRepo */
|
||||
typedef struct {
|
||||
OstreeRepo *repo;
|
||||
guint step;
|
||||
} LockThreadData;
|
||||
|
||||
static gpointer
|
||||
lock_thread1 (gpointer thread_data)
|
||||
{
|
||||
LockThreadData *data = thread_data;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
/* Step 0: Take an exclusive lock */
|
||||
g_assert_cmpuint (data->step, ==, 0);
|
||||
g_test_message ("Thread 1: Push exclusive lock");
|
||||
ostree_repo_lock_push (data->repo, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
data->step++;
|
||||
|
||||
/* Step 2: Take a shared lock */
|
||||
while (data->step != 2)
|
||||
g_thread_yield ();
|
||||
g_test_message ("Thread 1: Push shared lock");
|
||||
ostree_repo_lock_push (data->repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
data->step++;
|
||||
|
||||
/* Step 4: Pop both locks */
|
||||
while (data->step != 4)
|
||||
g_thread_yield ();
|
||||
g_test_message ("Thread 1: Pop shared lock");
|
||||
ostree_repo_lock_pop (data->repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_test_message ("Thread 1: Pop exclusive lock");
|
||||
ostree_repo_lock_pop (data->repo, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
data->step++;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
lock_thread2 (gpointer thread_data)
|
||||
{
|
||||
LockThreadData *data = thread_data;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
/* Step 1: Wait for the other thread to acquire a lock and then take a
|
||||
* shared lock.
|
||||
*/
|
||||
while (data->step != 1)
|
||||
g_thread_yield ();
|
||||
g_test_message ("Thread 2: Push shared lock");
|
||||
ostree_repo_lock_push (data->repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
data->step++;
|
||||
|
||||
/* Step 6: Pop lock */
|
||||
while (data->step != 6)
|
||||
g_thread_yield ();
|
||||
g_test_message ("Thread 2: Pop shared lock");
|
||||
ostree_repo_lock_pop (data->repo, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
data->step++;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
test_repo_lock_multi_thread (Fixture *fixture,
|
||||
gconstpointer test_data)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(OstreeRepo) repo1 = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_autoptr(OstreeRepo) repo2 = ostree_repo_open_at (fixture->tmpdir.fd, ".",
|
||||
NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
LockThreadData thread_data = {repo1, 0};
|
||||
GThread *thread1 = g_thread_new ("lock-thread-1", lock_thread1, &thread_data);
|
||||
GThread *thread2 = g_thread_new ("lock-thread-2", lock_thread2, &thread_data);
|
||||
|
||||
/* Step 3: Try to take a shared lock on repo2. This should fail since
|
||||
* thread1 still has an exclusive lock.
|
||||
*/
|
||||
while (thread_data.step != 3)
|
||||
g_thread_yield ();
|
||||
g_test_message ("Repo 2: Push failing shared lock");
|
||||
ostree_repo_lock_push (repo2, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
|
||||
g_clear_error (&error);
|
||||
thread_data.step++;
|
||||
|
||||
/* Step 5: Try to a lock on repo2. A shared lock should succeed since
|
||||
* thread1 has dropped its exclusive lock.
|
||||
*/
|
||||
while (thread_data.step != 5)
|
||||
g_thread_yield ();
|
||||
g_test_message ("Repo 2: Push shared lock");
|
||||
ostree_repo_lock_push (repo2, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_test_message ("Repo 2: Push failing exclusive lock");
|
||||
ostree_repo_lock_push (repo2, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
|
||||
g_clear_error (&error);
|
||||
thread_data.step++;
|
||||
|
||||
/* Step 7: Now both threads have dropped their locks and taking an exclusive
|
||||
* lock should succeed.
|
||||
*/
|
||||
while (thread_data.step != 7)
|
||||
g_thread_yield ();
|
||||
g_test_message ("Repo 2: Push exclusive lock");
|
||||
ostree_repo_lock_push (repo2, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_test_message ("Repo 2: Pop exclusive lock");
|
||||
ostree_repo_lock_pop (repo2, OSTREE_REPO_LOCK_EXCLUSIVE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_test_message ("Repo 2: Pop shared lock");
|
||||
ostree_repo_lock_pop (repo2, OSTREE_REPO_LOCK_SHARED, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
thread_data.step++;
|
||||
|
||||
g_thread_join (thread1);
|
||||
g_thread_join (thread2);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char **argv)
|
||||
@ -266,6 +584,20 @@ main (int argc,
|
||||
test_repo_get_min_free_space, teardown);
|
||||
g_test_add ("/repo/write_regfile_api", Fixture, NULL, setup,
|
||||
test_write_regfile_api, teardown);
|
||||
g_test_add ("/repo/autolock", Fixture, NULL, setup,
|
||||
test_repo_autolock, teardown);
|
||||
g_test_add ("/repo/lock/single", Fixture, NULL, lock_setup,
|
||||
test_repo_lock_single, teardown);
|
||||
g_test_add ("/repo/lock/unlock-never-locked", Fixture, NULL, lock_setup,
|
||||
test_repo_lock_unlock_never_locked, teardown);
|
||||
g_test_add ("/repo/lock/double-unlock", Fixture, NULL, lock_setup,
|
||||
test_repo_lock_double_unlock, teardown);
|
||||
g_test_add ("/repo/lock/unlock-wrong-type", Fixture, NULL, lock_setup,
|
||||
test_repo_lock_unlock_wrong_type, teardown);
|
||||
g_test_add ("/repo/lock/multi-repo", Fixture, NULL, lock_setup,
|
||||
test_repo_lock_multi_repo, teardown);
|
||||
g_test_add ("/repo/lock/multi-thread", Fixture, NULL, lock_setup,
|
||||
test_repo_lock_multi_thread, teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user