From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793719819; b=hfevAtzFGJO4cC1VCoMhCbxsJA7uqQ53u09/7nRG85f6o30r6LzoI5KIZnGkY6PQGy3j zWx2r/RsBT6WlJ7R6HMo829/4YuhuoHPFfXSiuUmyurAAMwvJvfMbv5urtlKepWn887Qq P6Zl9kb43cTE62E/jcb/DFLzUe5/0cziqivqIirLkjF7nka3NE7Qu1jWQtotMG3e2v6Fg IfJ3L85lNfealsSiFSRrfB9WPXHNNl4k4GGjH8RbtuF1unPXa6u6yXX5uVXkm90fP8SD2 Ut+ivhFqfRgBIGLChuUVLokw071KLPRX2TVezilFYX8PtLvaZ5OQlQjd5Ry8YgDajRA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793719819; h=Received:DKIM-Signature:Date:From:To:Cc:Subject:Message-ID: References:MIME-Version:Content-Type:Content-Disposition:In-Reply-To; bh=w0YiZkYFIskqCOLKCDBxDBgRUJ8hvITqJ7FNLO3QniE=; b=taNTD2vJq1vm9IS3lwqjfRUEhgjnjCgA+h+tabAzk3zSeohOJ1MUMQ4kLLMCqL0ypOmX whf8NdFZYEBqzv1I+oHdrfMu0fyAfRbbxnxGW9hInaQaJOIHeqHL2Z0OZHbUMBeLCY/z4 KyQGMaheK4JYMMd3nPVy2gIFjW1HhIeSeKGfGjSeYkmDarhjHYHH5N0SPF45yZdDPByK1 nbC/NwhW1rRppZy+YEaGGqKnDXAZvsq6Ejp7xpl/FFFW+eH7auPrdFoD/35r68T37anaX euOSKX6wrf32tLEVYtE8+8MYIfoh8rsSLagfMBkxs7w2r58RL5AAhylQ+q+S2ZfqzLQ== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=af4dvp5p; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with UTF8SMTPS id B8C9B606A33C; Tue, 3 Nov 2026 15:30:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=af4dvp5p Received: by smtp.kernel.org (Postfix) with UTF8SMTPSA id D01871F00A15; Tue, 3 Nov 2026 15:30:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1793719818; bh=w0YiZkYFIskqCOLKCDBxDBgRUJ8hvITqJ7FNLO3QniE=; h=Date:From:To:Cc:Subject:References:In-Reply-To; b=af4dvp5pKOrBPnvoV2mZ6b+kM1VKuUjDrBiFOI6f8cwoJ4s5qv1QsDls+Q4txGca8 UkszUoNGSVG2M9aAUI9o2kjIoqQMhKMCAywL0AI5UbW1aFI8vtw8bKksiu30S+x4lF rrtRNiGuaaLizAV47PN6fB5WvSf6zh410+e8euij4sU/5jWo1CXDSaS0zrhR8K2HSn bwGgbg9tV/OX9r/JT/yt/NFT3O/soO4xmbozzx/TqqaE+HE6tTryEiOhIsLL6r0G0i jpxDqAefkFO8Dv1EM5P3GG+490oqelfh5Y9Y9ZI59mQ1KzOWV8B11a+nER363xwEOY Aml96HjFZW4SA== Date: Tue, 3 Nov 2026 17:30:14 +0200 From: Jarkko Sakkinen To: David Howells Cc: Eric Biggers , linux-integrity@vger.kernel.org, Mimi Zohar , Roberto Sassu , Dmitry Kasatkin , Eric Snowberg , Paul Moore , James Morris , "Serge E. Hallyn" , "open list:SECURITY SUBSYSTEM" , open list , "open list:KEYS/KEYRINGS" Subject: Re: [PATCH v9] KEYS: key_garbage_collector(): process in chunks Message-ID: References: <20261030205412.3138975-1-jarkko@kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20261030205412.3138975-1-jarkko@kernel.org> On Tue, Nov 03, 2026 at 02:36:29PM +0200, Jarkko Sakkinen wrote: > key_garbage_collector() has a design that is not resistant enough for > workqueue lockups. The duration scales up relative to the number of items > in the serial tree, which causes a reoccuring theme of being guilty of > causing workqueue lockups. > > Resolve the problem by processing in chunks: > > 1. Declare a static variable key_gc_work, which owns the garbage collection > in-flight state, and key_schedule_gc_work() for re-scheduling the work. > 2. Define KEY_GC_CHUNK_SIZE, set to 64 keys, as the bound for the number > of items scanned and destroyed. > 3. Define KEY_GC_DESTROYING to mark resume to destruction without repeating > the RCU grace period. Drain the graveyard before advancing key-type > reaping. > 4. Define KEY_GC_REAP_REQUESTED for preserving full-pass requests across > the continuations. > > keygcbench results without the patch: > > unused: min=798.785 avg=4181.989 max=7565.193 var=11446068.927 > expired: min=0.003 avg=2404.971 max=4111.065 var=3061018.501 > combined: min=0.003 avg=3115.778 max=7565.193 var=7172908.943 > > keygcbench results with the patch: > > unused: min=0.005 avg=0.125 max=79.590 var=1.088 > expired: min=0.002 avg=0.196 max=236.207 var=8.121 > combined: min=0.002 avg=0.154 max=236.207 var=3.912 > > These results translate to: > > - 93% drop in the combined variance. > - 2.2% increase in the total task time. > > The value 64 for KEY_GC_CHUNK_SIZE was cherry-picked from the set {1, 2, > ..., 1024} by running the benchmark for each chunk size three times. > > At the far left variance is 97% lower but at the same time total time > grows by 133%, when compared to the selected chunk size. The size was > picked based on getting a reasonable variance with a minimal throughput > decline. > > Link: https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd-test.git/tree/package/keygcbench > Signed-off-by: Jarkko Sakkinen I also think 64 keys is somewhat common sense number overall. Not many real-world keyring instances have too multiple chunks. > --- > v9: > - Removed spurious syzkaller tags. > - Removed Fixes tag. This is better to be treated as improvement as > lockups are still very rare and quite artificial. > - No code changes. > v8: > - Call key_schedule_gc_work(false) from key_put() to avoid unnecessary > expired-link scans when freeing unreferenced keys, as reported by > Sashiko: > https://sashiko.dev/#/patchset/20261030202457.3058665-1-jarkko%40kernel.org > v7: > - Removed forgotten Closes tag for syzkaller. > - Sashiko was right in that key_gc_schedule_links() should be called to > mark expiry [1]. I fixed up the call site and also added expired bool > parameter to key_gc_schedule_work() so that bits can be set in a single > atomic operation. > > https://sashiko.dev/#/patchset/20261030160924.2919363-1-jarkko%40kernel.org > v6: > - Removed syzkaller references, given feedback: > https://lore.kernel.org/keyrings/20261029234149.218-1-hdanton@sina.com/ > v5: > - Benchmarked all the chunk sizes in {1, 2, ..., 1024} and cherry-picked > based on that the chunk size of 64. > v4: > - Added syzkaller link, as the resolution can be reasoned from the benchmark > results. > v3: > - Added new tests results made by using keygcbench. > - Added link to keygcbench. > v2: > - Fixed typo in the commit message. > - Drop the local variable gc_state. > security/keys/gc.c | 162 +++++++++++++++++++++++++-------------- > security/keys/internal.h | 3 +- > security/keys/key.c | 4 +- > security/keys/keyring.c | 2 +- > 4 files changed, 107 insertions(+), 64 deletions(-) > > diff --git a/security/keys/gc.c b/security/keys/gc.c > index eda445f815d4..ea2efde5f33f 100644 > --- a/security/keys/gc.c > +++ b/security/keys/gc.c > @@ -10,16 +10,27 @@ > #include > #include "internal.h" > > -/* > - * Delay between key revocation/expiry in seconds > - */ > -unsigned key_gc_delay = 5 * 60; > +#define KEY_GC_CHUNK_SIZE 64 > > /* > * Reaper for unused keys. > */ > static void key_garbage_collector(struct work_struct *work); > -DECLARE_WORK(key_gc_work, key_garbage_collector); > + > +static struct { > + time64_t start; /* expiry range start */ > + time64_t end; /* expiry range end */ > + struct rb_node *cursor; /* serial tree position */ > + u8 state; /* state flags */ > + struct work_struct work; /* work item */ > +} key_gc_work = { > + .work = __WORK_INITIALIZER(key_gc_work.work, key_garbage_collector), > +}; > + > +/* > + * Delay between key revocation/expiry in seconds > + */ > +unsigned int key_gc_delay = 5 * 60; > > /* > * Reaper for links from keyrings to dead keys. > @@ -34,6 +45,7 @@ static unsigned long key_gc_flags; > #define KEY_GC_KEY_EXPIRED 0 /* A key expired and needs unlinking */ > #define KEY_GC_REAP_KEYTYPE 1 /* A keytype is being unregistered */ > #define KEY_GC_REAPING_KEYTYPE 2 /* Cleared when keytype reaped */ > +#define KEY_GC_REAP_REQUESTED 3 /* Another complete pass was requested */ > > > /* > @@ -44,6 +56,21 @@ struct key_type key_type_dead = { > .name = ".dead", > }; > > +/* > + * Schedule a new GC pass or re-schedule an ongoing GC pass. > + */ > +void key_schedule_gc_work(bool expired) > +{ > + unsigned long flags = BIT(KEY_GC_REAP_REQUESTED); > + > + if (expired) > + flags |= BIT(KEY_GC_KEY_EXPIRED); > + > + /* Publish all request flags together, after the caller's updates. */ > + set_mask_bits(&key_gc_flags, 0, flags); > + schedule_work(&key_gc_work.work); > +} > + > /* > * Schedule a garbage collection run. > * - time precision isn't particularly important > @@ -57,7 +84,8 @@ void key_schedule_gc(time64_t gc_at) > > if (gc_at <= now || test_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) { > kdebug("IMMEDIATE"); > - schedule_work(&key_gc_work); > + /* Catch expirations that became due during the scan. */ > + key_schedule_gc_work(true); > } else if (gc_at < key_gc_next_run) { > kdebug("DEFERRED"); > key_gc_next_run = gc_at; > @@ -79,15 +107,6 @@ void key_set_expiry(struct key *key, time64_t expiry) > } > } > > -/* > - * Schedule a dead links collection run. > - */ > -void key_schedule_gc_links(void) > -{ > - set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); > - schedule_work(&key_gc_work); > -} > - > /* > * Some key's cleanup time was met after it expired, so we need to get the > * reaper to go through a cycle finding expired keys. > @@ -96,7 +115,7 @@ static void key_gc_timer_func(struct timer_list *unused) > { > kenter(""); > key_gc_next_run = TIME64_MAX; > - key_schedule_gc_links(); > + key_schedule_gc_work(true); > } > > /* > @@ -119,7 +138,7 @@ void key_gc_keytype(struct key_type *ktype) > set_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags); > > kdebug("schedule"); > - schedule_work(&key_gc_work); > + key_schedule_gc_work(false); > > kdebug("sleep"); > wait_on_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE, > @@ -134,7 +153,9 @@ void key_gc_keytype(struct key_type *ktype) > */ > static noinline void key_gc_unused_keys(struct list_head *keys) > { > - while (!list_empty(keys)) { > + unsigned int chunk = KEY_GC_CHUNK_SIZE; > + > + while (!list_empty(keys) && chunk--) { > struct key *key = > list_entry(keys->next, struct key, graveyard_link); > short state = key->state; > @@ -178,54 +199,64 @@ static noinline void key_gc_unused_keys(struct list_head *keys) > static void key_garbage_collector(struct work_struct *work) > { > static LIST_HEAD(graveyard); > - static u8 gc_state; /* Internal persistent state */ > + typeof(key_gc_work) *gc = &key_gc_work; > #define KEY_GC_REAP_AGAIN 0x01 /* - Need another cycle */ > #define KEY_GC_REAPING_LINKS 0x02 /* - We need to reap links */ > +#define KEY_GC_DESTROYING 0x04 /* - We need to destroy keys */ > #define KEY_GC_REAPING_DEAD_1 0x10 /* - We need to mark dead keys */ > #define KEY_GC_REAPING_DEAD_2 0x20 /* - We need to reap dead key links */ > #define KEY_GC_REAPING_DEAD_3 0x40 /* - We need to reap dead keys */ > #define KEY_GC_FOUND_DEAD_KEY 0x80 /* - We found at least one dead key */ > > - struct rb_node *cursor; > + unsigned int chunk = KEY_GC_CHUNK_SIZE; > struct key *key; > - time64_t new_timer, limit, expiry; > + time64_t expiry; > > - kenter("[%lx,%x]", key_gc_flags, gc_state); > + kenter("[%lx,%x]", key_gc_flags, gc->state); > > - limit = ktime_get_real_seconds(); > + if (gc->state & KEY_GC_DESTROYING) > + goto destroy_keys; > + if (gc->cursor) { > + spin_lock(&key_serial_lock); > + goto continue_scanning; > + } > + > + test_and_clear_bit(KEY_GC_REAP_REQUESTED, &key_gc_flags); > + gc->start = ktime_get_real_seconds(); > > /* Work out what we're going to be doing in this pass */ > - gc_state &= KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2; > - gc_state <<= 1; > + gc->state &= KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2; > + gc->state <<= 1; > if (test_and_clear_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags)) > - gc_state |= KEY_GC_REAPING_LINKS; > + gc->state |= KEY_GC_REAPING_LINKS; > > if (test_and_clear_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) > - gc_state |= KEY_GC_REAPING_DEAD_1; > - kdebug("new pass %x", gc_state); > + gc->state |= KEY_GC_REAPING_DEAD_1; > + kdebug("new pass %x", gc->state); > > - new_timer = TIME64_MAX; > + gc->end = TIME64_MAX; > > /* As only this function is permitted to remove things from the key > * serial tree, if cursor is non-NULL then it will always point to a > * valid node in the tree - even if lock got dropped. > */ > spin_lock(&key_serial_lock); > - cursor = rb_first(&key_serial_tree); > + gc->cursor = rb_first(&key_serial_tree); > > continue_scanning: > - while (cursor) { > - key = rb_entry(cursor, struct key, serial_node); > - cursor = rb_next(cursor); > + while (gc->cursor && chunk) { > + key = rb_entry(gc->cursor, struct key, serial_node); > + gc->cursor = rb_next(gc->cursor); > + chunk--; > > if (!test_bit_acquire(KEY_FLAG_USER_ALIVE, &key->flags)) { > /* Clobber key->user after final put seen. */ > goto found_unreferenced_key; > } > > - if (unlikely(gc_state & KEY_GC_REAPING_DEAD_1)) { > + if (unlikely(gc->state & KEY_GC_REAPING_DEAD_1)) { > if (key->type == key_gc_dead_keytype) { > - gc_state |= KEY_GC_FOUND_DEAD_KEY; > + gc->state |= KEY_GC_FOUND_DEAD_KEY; > set_bit(KEY_FLAG_DEAD, &key->flags); > key->perm = 0; > goto skip_dead_key; > @@ -239,24 +270,24 @@ static void key_garbage_collector(struct work_struct *work) > if (expiry != TIME64_MAX) { > if (!(key->type->flags & KEY_TYPE_INSTANT_REAP)) > expiry += key_gc_delay; > - if (expiry > limit && expiry < new_timer) { > + if (expiry > gc->start && expiry < gc->end) { > kdebug("will expire %x in %lld", > - key_serial(key), key->expiry - limit); > - new_timer = key->expiry; > + key_serial(key), key->expiry - gc->start); > + gc->end = key->expiry; > } > } > > - if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) > + if (unlikely(gc->state & KEY_GC_REAPING_DEAD_2)) > if (key->type == key_gc_dead_keytype) > - gc_state |= KEY_GC_FOUND_DEAD_KEY; > + gc->state |= KEY_GC_FOUND_DEAD_KEY; > > - if ((gc_state & KEY_GC_REAPING_LINKS) || > - unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { > + if ((gc->state & KEY_GC_REAPING_LINKS) || > + unlikely(gc->state & KEY_GC_REAPING_DEAD_2)) { > if (key->type == &key_type_keyring) > goto found_keyring; > } > > - if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) > + if (unlikely(gc->state & KEY_GC_REAPING_DEAD_3)) > if (key->type == key_gc_dead_keytype) > goto destroy_dead_key; > > @@ -269,7 +300,11 @@ static void key_garbage_collector(struct work_struct *work) > spin_unlock(&key_serial_lock); > > maybe_resched: > - if (cursor) { > + if (gc->cursor) { > + if (!chunk) { > + schedule_work(&key_gc_work.work); > + return; > + } > cond_resched(); > spin_lock(&key_serial_lock); > goto continue_scanning; > @@ -281,12 +316,12 @@ static void key_garbage_collector(struct work_struct *work) > */ > kdebug("pass complete"); > > - if (new_timer != TIME64_MAX) { > - new_timer += key_gc_delay; > - key_schedule_gc(new_timer); > + if (gc->end != TIME64_MAX) { > + gc->end += key_gc_delay; > + key_schedule_gc(gc->end); > } > > - if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2) || > + if (unlikely(gc->state & KEY_GC_REAPING_DEAD_2) || > !list_empty(&graveyard)) { > /* Make sure that all pending keyring payload destructions are > * fulfilled and that people aren't now looking at dead or > @@ -297,33 +332,42 @@ static void key_garbage_collector(struct work_struct *work) > synchronize_rcu(); > } > > + gc->state |= KEY_GC_DESTROYING; > + > +destroy_keys: > if (!list_empty(&graveyard)) { > kdebug("gc keys"); > key_gc_unused_keys(&graveyard); > + if (!list_empty(&graveyard)) { > + schedule_work(&key_gc_work.work); > + return; > + } > } > + gc->state &= ~KEY_GC_DESTROYING; > > - if (unlikely(gc_state & (KEY_GC_REAPING_DEAD_1 | > + if (unlikely(gc->state & (KEY_GC_REAPING_DEAD_1 | > KEY_GC_REAPING_DEAD_2))) { > - if (!(gc_state & KEY_GC_FOUND_DEAD_KEY)) { > + if (!(gc->state & KEY_GC_FOUND_DEAD_KEY)) { > /* No remaining dead keys: short circuit the remaining > * keytype reap cycles. > */ > kdebug("dead short"); > - gc_state &= ~(KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2); > - gc_state |= KEY_GC_REAPING_DEAD_3; > + gc->state &= ~(KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2); > + gc->state |= KEY_GC_REAPING_DEAD_3; > } else { > - gc_state |= KEY_GC_REAP_AGAIN; > + gc->state |= KEY_GC_REAP_AGAIN; > } > } > > - if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { > + if (unlikely(gc->state & KEY_GC_REAPING_DEAD_3)) { > kdebug("dead wake"); > clear_and_wake_up_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); > } > > - if (gc_state & KEY_GC_REAP_AGAIN) > - schedule_work(&key_gc_work); > - kleave(" [end %x]", gc_state); > + if ((gc->state & KEY_GC_REAP_AGAIN) || > + test_bit(KEY_GC_REAP_REQUESTED, &key_gc_flags)) > + schedule_work(&key_gc_work.work); > + kleave(" [end %x]", gc->state); > return; > > /* We found an unreferenced key - once we've removed it from the tree, > @@ -335,7 +379,7 @@ static void key_garbage_collector(struct work_struct *work) > spin_unlock(&key_serial_lock); > > list_add_tail(&key->graveyard_link, &graveyard); > - gc_state |= KEY_GC_REAP_AGAIN; > + gc->state |= KEY_GC_REAP_AGAIN; > goto maybe_resched; > > /* We found a restricted keyring and need to update the restriction if > @@ -353,7 +397,7 @@ static void key_garbage_collector(struct work_struct *work) > */ > found_keyring: > spin_unlock(&key_serial_lock); > - keyring_gc(key, limit); > + keyring_gc(key, gc->start); > goto maybe_resched; > > /* We found a dead key that is still referenced. Reset its type and > diff --git a/security/keys/internal.h b/security/keys/internal.h > index b7b622bc36a1..33aa9bce3bb9 100644 > --- a/security/keys/internal.h > +++ b/security/keys/internal.h > @@ -162,14 +162,13 @@ extern bool lookup_user_key_possessed(const struct key *key, > extern long join_session_keyring(const char *name); > extern void key_change_session_keyring(struct callback_head *twork); > > -extern struct work_struct key_gc_work; > extern unsigned key_gc_delay; > extern void keyring_gc(struct key *keyring, time64_t limit); > extern void keyring_restriction_gc(struct key *keyring, > struct key_type *dead_type); > void key_set_expiry(struct key *key, time64_t expiry); > extern void key_schedule_gc(time64_t gc_at); > -extern void key_schedule_gc_links(void); > +void key_schedule_gc_work(bool expired); > extern void key_gc_keytype(struct key_type *ktype); > > extern int key_task_permission(const key_ref_t key_ref, > diff --git a/security/keys/key.c b/security/keys/key.c > index e88548f1a704..dd7a72c09cc9 100644 > --- a/security/keys/key.c > +++ b/security/keys/key.c > @@ -670,7 +670,7 @@ void key_put(struct key *key) > } > /* Mark key as safe for GC after key->user done. */ > clear_bit_unlock(KEY_FLAG_USER_ALIVE, &key->flags); > - schedule_work(&key_gc_work); > + key_schedule_gc_work(false); > } > } > } > @@ -1189,7 +1189,7 @@ void key_invalidate(struct key *key) > down_write_nested(&key->sem, 1); > if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) { > notify_key(key, NOTIFY_KEY_INVALIDATED, 0); > - key_schedule_gc_links(); > + key_schedule_gc_work(true); > } > up_write(&key->sem); > } > diff --git a/security/keys/keyring.c b/security/keys/keyring.c > index 5281da9d5bf7..40e3b2b6aca1 100644 > --- a/security/keys/keyring.c > +++ b/security/keys/keyring.c > @@ -265,7 +265,7 @@ void key_remove_domain(struct key_tag *domain_tag) > { > domain_tag->removed = true; > if (!key_put_tag(domain_tag)) > - key_schedule_gc_links(); > + key_schedule_gc_work(true); > } > > /* > -- > 2.47.3 > Br, Jarkko