summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libostree/ostree-repo-commit.c4
-rw-r--r--src/libostree/ostree-repo.c182
-rw-r--r--src/libostree/ostree-repo.h9
-rwxr-xr-xtests/test-core.js6
4 files changed, 131 insertions, 70 deletions
diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c
index dd5cd862..ffd8319f 100644
--- a/src/libostree/ostree-repo-commit.c
+++ b/src/libostree/ostree-repo-commit.c
@@ -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;
}
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
index 1036a81f..61ee327b 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -214,7 +214,8 @@ static GPrivate repo_lock_table = G_PRIVATE_INIT (free_repo_lock_table);
typedef struct {
int fd;
- GQueue stack;
+ guint shared; /* Number of shared locks */
+ guint exclusive; /* Number of exclusive locks */
} OstreeRepoLock;
typedef struct {
@@ -223,6 +224,22 @@ typedef struct {
const char *name;
} OstreeRepoLockInfo;
+static const char *
+lock_state_name (int state)
+{
+ switch (state)
+ {
+ case LOCK_EX:
+ return "exclusive";
+ case LOCK_SH:
+ return "shared";
+ case LOCK_UN:
+ return "unlocked";
+ default:
+ g_assert_not_reached ();
+ }
+}
+
static void
repo_lock_info (OstreeRepoLock *lock, OstreeRepoLockInfo *out_info)
{
@@ -230,17 +247,14 @@ repo_lock_info (OstreeRepoLock *lock, OstreeRepoLockInfo *out_info)
g_assert (out_info != NULL);
OstreeRepoLockInfo info;
- info.len = g_queue_get_length (&lock->stack);
+ info.len = lock->shared + lock->exclusive;
if (info.len == 0)
- {
info.state = LOCK_UN;
- info.name = "unlocked";
- }
+ else if (lock->exclusive > 0)
+ info.state = LOCK_EX;
else
- {
- info.state = GPOINTER_TO_INT (g_queue_peek_head (&lock->stack));
- info.name = (info.state == LOCK_EX) ? "exclusive" : "shared";
- }
+ info.state = LOCK_SH;
+ info.name = lock_state_name (info.state);
*out_info = info;
}
@@ -256,7 +270,6 @@ free_repo_lock (gpointer data)
repo_lock_info (lock, &info);
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");
@@ -339,6 +352,7 @@ 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;
@@ -355,7 +369,6 @@ push_repo_lock (OstreeRepo *self,
if (lock == NULL)
{
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,
@@ -374,31 +387,42 @@ push_repo_lock (OstreeRepo *self,
repo_lock_info (lock, &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 = &(lock->exclusive);
+ else
+ counter = &(lock->shared);
+
+ /* Check for overflow */
+ g_assert_cmpuint (*counter, <, G_MAXUINT);
+
+ 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))
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;
@@ -412,34 +436,57 @@ pop_repo_lock (OstreeRepo *self,
OstreeRepoLockInfo info;
repo_lock_info (lock, &info);
g_return_val_if_fail (info.len > 0, FALSE);
-
g_debug ("Pop lock: state=%s, depth=%u", info.name, info.len);
- if (info.len > 1)
+
+ int state_to_drop;
+ guint *counter;
+ if (lock_type == OSTREE_REPO_LOCK_EXCLUSIVE)
+ {
+ state_to_drop = LOCK_EX;
+ counter = &(lock->exclusive);
+ }
+ else
{
- int next_state = GPOINTER_TO_INT (g_queue_peek_nth (&lock->stack, 1));
+ state_to_drop = LOCK_SH;
+ counter = &(lock->shared);
+ }
- /* 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);
+ /* Make sure caller specified a valid type to release */
+ g_assert_cmpuint (*counter, >, 0);
+
+ 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 = (lock->exclusive > 1) ? LOCK_EX : LOCK_SH;
else
+ next_state = (lock->exclusive > 0) ? LOCK_EX : LOCK_SH;
+
+ if (next_state == LOCK_UN)
{
- /* 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");
}
+ 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_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");
+ }
- g_queue_pop_head (&lock->stack);
+ /* Update state */
+ (*counter)--;
return TRUE;
}
@@ -451,13 +498,13 @@ pop_repo_lock (OstreeRepo *self,
* @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
@@ -542,13 +589,16 @@ ostree_repo_lock_push (OstreeRepo *self,
/**
* 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
@@ -564,9 +614,10 @@ ostree_repo_lock_push (OstreeRepo *self,
* 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);
@@ -583,7 +634,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
{
@@ -598,7 +649,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,
@@ -629,18 +680,22 @@ ostree_repo_lock_pop (OstreeRepo *self,
}
}
-/*
+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;
@@ -660,7 +715,11 @@ ostree_repo_auto_lock_push (OstreeRepo *self,
{
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;
}
/**
@@ -674,18 +733,19 @@ ostree_repo_auto_lock_push (OstreeRepo *self,
* 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);
}
}
diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h
index 62e238c9..08d3d408 100644
--- a/src/libostree/ostree-repo.h
+++ b/src/libostree/ostree-repo.h
@@ -1519,9 +1519,10 @@ gboolean ostree_repo_lock_push (OstreeRepo *self,
GCancellable *cancellable,
GError **error);
_OSTREE_PUBLIC
-gboolean ostree_repo_lock_pop (OstreeRepo *self,
- GCancellable *cancellable,
- GError **error);
+gboolean ostree_repo_lock_pop (OstreeRepo *self,
+ OstreeRepoLockType lock_type,
+ GCancellable *cancellable,
+ GError **error);
/* C convenience API only */
#ifndef __GI_SCANNER__
@@ -1533,7 +1534,7 @@ gboolean ostree_repo_lock_pop (OstreeRepo *self,
*
* Since: 2021.3
*/
-typedef OstreeRepo OstreeRepoAutoLock;
+typedef struct OstreeRepoAutoLock OstreeRepoAutoLock;
_OSTREE_PUBLIC
OstreeRepoAutoLock * ostree_repo_auto_lock_push (OstreeRepo *self,
diff --git a/tests/test-core.js b/tests/test-core.js
index 7d871c1f..b804e007 100755
--- a/tests/test-core.js
+++ b/tests/test-core.js
@@ -137,9 +137,9 @@ 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(null);
-repo.lock_pop(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(null);
+repo.lock_pop(OSTree.RepoLockType.EXCLUSIVE, null);
print("ok test-core");