Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework OstreeAsyncProgress API to use GVariant #819

Closed
wants to merge 6 commits into from

Conversation

pwithnall
Copy link
Member

I spent a little while today looking at different progress reporting APIs, and it turns out that despite @cgwalters’ reservations, OstreeAsyncProgress is pretty much ideal. I’ve pasted my analysis at the bottom of this PR.

This PR adds a few missing features to OstreeAsyncProgress which will allow it to be used by the LAN/USB work (PR #782) to give structured information about each ref it’s pulling, for example, by passing GVariant dicts in the progress information.

The PR also improves the atomicity of accesses to an OstreeAsyncProgress instance, which fixes some potential bugs in reporting inconsistent pull progress.


Options for progress reporting:

  • EvPrintOperation: have an object for the entire operation, report progress via its properties or methods
    • + Nicely encapsulates the lifetime of the operation (can be queried after the operation finishes)
    • − Properties aren’t threadsafe
    • + Easily extensible
    • + Typesafe
    • + Supports notification (notify signal)
    • − Not a standard interface (a new object has to be implemented for each operation)
    • − Queries are not atomic (different properties queried at different times)
  • FlatpakProgressCallback, GFileProgressCallback: caller provides a callback which has a fixed set of parameters
    • − Doesn’t encapsulate the lifetime of the operation (can’t be queried afterwards)
    • + Threadsafe
    • − Not extensible
    • + Supports notification (the callback itself)
    • + State can be queried atomically (all properties are returned atomically)
    • + Standard interface
    • + Typesafe
  • OstreeAsyncProgress: caller provides an object which has a notification callback associated with it
    • + Fairly well encapsulates the lifetime of the operation (can be queried after the operation finishes)
    • + Threadsafe
    • + Easily extensible (though more complex types are a bit of a pain)
    • − Not typesafe (relies on documentation), but subclasses can add type safety
    • + Supports notification (callback)
    • + Standard interface
    • − Queries are not atomic (different properties queried at different times), but this could be fixed easily

OstreeAsyncProgress currently does some contortions to try and avoid
allocating space for guints and guint64s (on 64-bit platforms), but this
means it uses two GHashTables. A GHashTable allocates 8 buckets even
when empty. Given that the largest usage of OstreeAsyncProgress in
libostree puts 13 uints and 5 uint64s in it, this optimisation does not
save significant (if any) memory.

Instead, change OstreeAsyncProgress to store values internally as
GVariants, and expose this with some new API:
 • ostree_async_progress_get_variant()
 • ostree_async_progress_set_variant()
Each GVariant is allocated on the heap. As they are immutable, they are
thread-safe once returned by a getter.

The existing API continues to work as before, except in the case where a
key is set/got as both a uint and a uint64 — there will now be a
collision (and a GVariant type checking failure) whereas previously
there was no collision. Nothing in OSTree uses OstreeAsyncProgress this
way though.

The new API can be used to share more complex data via the progress API.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
OstreeAsyncProgress is thread-safe: it can have keys changed by one
thread while another is getting the same keys (modulo some locking
contention). However, the thread safety is done at the function call
level: if some code calls an OstreeAsyncProgress getter several times,
the key fetches are not atomic with respect to each other.

In the case of contention on the lock, this can result in consumers of
OstreeAsyncProgress data seeing an inconsistent state between the
properties they query, which could result in progress reporting
inaccuracies.

In the uncontested case, this results in the OstreeAsyncProgress lock
being locked and unlocked many times more than necessary.

Try to improve this by adding new API, which supports getting and
setting multiple keys atomically:
 • ostree_async_progress_get()
 • ostree_async_progress_set()

The new API uses GVariants and varargs: keys are passed as a
GVariantType string followed by arguments as for g_variant_new() or
g_variant_get(), followed by the next key, etc.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
This will eliminate most of the potential races in progress reporting.
ostree_repo_pull_default_console_progress_changed() still calls three
getters, so there may still be races there, however.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
Rework how the status is handled in OstreeAsyncProgress so that it’s now
a well-known key in the hash table. This means that it can be retrieved
and set atomically with other keys using
ostree_async_progress_[get|set]().

The behaviour of ostree_async_progress_[get|set]_status() is preserved,
with the caveat that `status` can now also be accessed using the other
API on OstreeAsyncProgress, and has to be accessed with the right
GVariant type.

Internally, a NULL status is represented by an empty status string
(since ostree_async_progress_[get|set]_variant() deliberately don’t
allow NULL variants to be set against keys, since that would break the
ostree_async_progress_get() API).

Signed-off-by: Philip Withnall <withnall@endlessm.com>
Use the new well-known `status` key for OstreeAsyncProgress to get and
set the status atomically with other keys in an OstreeAsyncProgress
instance.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
@cgwalters
Copy link
Member

The flatpak failure isn't your fault, I submitted a PR to fix it.

The travis one though looks real:
../src/libostree/ostree-async-progress.c:148:3: error: implicit declaration of function 'g_autoptr' [-Werror=implicit-function-declaration]

I think we just need an #include "libglnx.h".

Otherwise: Awesome! A++, would review again!

@cgwalters
Copy link
Member

@rh-atomic-bot r+ 08d4a98

@rh-atomic-bot
Copy link

⌛ Testing commit 08d4a98 with merge cbe3989...

rh-atomic-bot pushed a commit that referenced this pull request Apr 29, 2017
OstreeAsyncProgress is thread-safe: it can have keys changed by one
thread while another is getting the same keys (modulo some locking
contention). However, the thread safety is done at the function call
level: if some code calls an OstreeAsyncProgress getter several times,
the key fetches are not atomic with respect to each other.

In the case of contention on the lock, this can result in consumers of
OstreeAsyncProgress data seeing an inconsistent state between the
properties they query, which could result in progress reporting
inaccuracies.

In the uncontested case, this results in the OstreeAsyncProgress lock
being locked and unlocked many times more than necessary.

Try to improve this by adding new API, which supports getting and
setting multiple keys atomically:
 • ostree_async_progress_get()
 • ostree_async_progress_set()

The new API uses GVariants and varargs: keys are passed as a
GVariantType string followed by arguments as for g_variant_new() or
g_variant_get(), followed by the next key, etc.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Closes: #819
Approved by: cgwalters
rh-atomic-bot pushed a commit that referenced this pull request Apr 29, 2017
This will eliminate most of the potential races in progress reporting.
ostree_repo_pull_default_console_progress_changed() still calls three
getters, so there may still be races there, however.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Closes: #819
Approved by: cgwalters
rh-atomic-bot pushed a commit that referenced this pull request Apr 29, 2017
Rework how the status is handled in OstreeAsyncProgress so that it’s now
a well-known key in the hash table. This means that it can be retrieved
and set atomically with other keys using
ostree_async_progress_[get|set]().

The behaviour of ostree_async_progress_[get|set]_status() is preserved,
with the caveat that `status` can now also be accessed using the other
API on OstreeAsyncProgress, and has to be accessed with the right
GVariant type.

Internally, a NULL status is represented by an empty status string
(since ostree_async_progress_[get|set]_variant() deliberately don’t
allow NULL variants to be set against keys, since that would break the
ostree_async_progress_get() API).

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Closes: #819
Approved by: cgwalters
rh-atomic-bot pushed a commit that referenced this pull request Apr 29, 2017
Use the new well-known `status` key for OstreeAsyncProgress to get and
set the status atomically with other keys in an OstreeAsyncProgress
instance.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Closes: #819
Approved by: cgwalters
@rh-atomic-bot
Copy link

☀️ Test successful - status-atomicjenkins
Approved by: cgwalters
Pushing cbe3989 to master...

@pwithnall pwithnall deleted the async-progress branch May 1, 2017 09:30
@dbnicholson
Copy link
Member

I'm hitting an assertion with this:

$ ostree --repo=test pull build app/org.gnome.Gnote/x86_64/eos3

**
OSTree:ERROR:src/libostree/ostree-async-progress.c:214:ostree_async_progress_get: assertion failed: (variant != NULL)
Aborted (core dumped)

This seems to happen from ostree_async_progress_finish when nothing has actually been pulled because I already have the commit. It emits the changed signal even though nothing's changed. Then the ostree_async_progress_get in ostree_repo_pull_default_console_progress_changed blows up because there's no data in the hash table. Here's the backtrace:

#0  0x00007ffff671fce7 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007ffff67213d8 in __GI_abort () at abort.c:89
#2  0x00007ffff6dd8355 in g_assertion_message (domain=domain@entry=0x7ffff79a0e3c "OSTree", file=file@entry=0x7ffff79a0eb0 "src/libostree/ostree-async-progress.c", line=line@entry=214, func=func@entry=0x7ffff79a0f50 <__func__.25193> "ostree_async_progress_get", message=message@entry=0x7b4710 "assertion failed: (variant != NULL)") at /usr/src/packages/BUILD/glib2.0-2.50.2+dev22.0cb2266/./glib/gtestutils.c:2432
#3  0x00007ffff6dd83ea in g_assertion_message_expr (domain=domain@entry=0x7ffff79a0e3c "OSTree", file=file@entry=0x7ffff79a0eb0 "src/libostree/ostree-async-progress.c", line=line@entry=214, func=func@entry=0x7ffff79a0f50 <__func__.25193> "ostree_async_progress_get", expr=expr@entry=0x7ffff79a0e65 "variant != NULL") at /usr/src/packages/BUILD/glib2.0-2.50.2+dev22.0cb2266/./glib/gtestutils.c:2455
#4  0x00007ffff7954bfe in ostree_async_progress_get (self=0x641140 [OstreeAsyncProgress]) at src/libostree/ostree-async-progress.c:214
#5  0x00007ffff7962ebd in ostree_repo_pull_default_console_progress_changed (progress=0x641140 [OstreeAsyncProgress], user_data=0x5fad) at src/libostree/ostree-repo.c:3734
#6  0x00007ffff708c224 in _g_closure_invoke_va (closure=0x5fad, closure@entry=0x64d450, return_value=0x5fad, return_value@entry=0x0, instance=0x6, instance@entry=0x641140, args=0x7ffff671fce7 <__GI_raise+55>, args@entry=0x7fffffffda30, n_params=0, param_types=0x7b4d90) at /usr/src/packages/BUILD/glib2.0-2.50.2+dev22.0cb2266/./gobject/gclosure.c:867
#7  0x00007ffff70a6747 in g_signal_emit_valist (instance=0x641140, signal_id=<optimized out>, detail=0, var_args=var_args@entry=0x7fffffffda30) at /usr/src/packages/BUILD/glib2.0-2.50.2+dev22.0cb2266/./gobject/gsignal.c:3300
#8  0x00007ffff70a70bf in g_signal_emit (instance=<optimized out>, signal_id=<optimized out>, detail=<optimized out>) at /usr/src/packages/BUILD/glib2.0-2.50.2+dev22.0cb2266/./gobject/gsignal.c:3447
#9  0x000000000041b50c in ostree_builtin_pull (argc=3, argv=0x7fffffffde18, cancellable=0x0, error=0x7fffffffdcc0) at src/ostree/ot-builtin-pull.c:323
#10 0x000000000041306e in ostree_run (argc=4, argv=0x7fffffffde18, commands=0x62c1c0 <commands>, res_error=0x7fffffffdd10) at src/ostree/ot-main.c:200
#11 0x000000000040a9dc in main (argc=5, argv=0x7fffffffde18) at src/ostree/main.c:78

pwithnall added a commit to pwithnall/ostree that referenced this pull request May 5, 2017
If one of the progress keys is set in a pull operation, a ::changed
signal is emitted on the progress object, and the callback for that
could query any of the progress keys — so they all need to be set,
otherwise we get an assertion failure in ostree_async_progress_get() due
to a named key not existing.

Spotted by Dan Nicholson in PR ostreedev#819.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
@pwithnall
Copy link
Member Author

I can’t reproduce the exact crash you’re seeing, but I think I can see where the problem is. ⇒ #835.

rh-atomic-bot pushed a commit that referenced this pull request May 5, 2017
If one of the progress keys is set in a pull operation, a ::changed
signal is emitted on the progress object, and the callback for that
could query any of the progress keys — so they all need to be set,
otherwise we get an assertion failure in ostree_async_progress_get() due
to a named key not existing.

Spotted by Dan Nicholson in PR #819.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Closes: #835
Approved by: cgwalters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants