gfs2 changes

- Don't cache unreferenced glocks: when a glock is no longer referenced (for
   example, because the inode it protects is evicted), it is now released as
   soon as possible instead of leaving it around until memory pressure or an
   unmount forces it out.  For some workloads, this saves a lot of memory and
   speeds up unmounts significantly.
 
 - Harden gfs2_glock_hold() by making sure the caller holds a reference and
   fix a related race in checking for the liveliness of glocks between
   gdlm_bast() and gfs2_glock_cb().
 -----BEGIN PGP SIGNATURE-----
 
 iQJIBAABCgAyFiEEJZs3krPW0xkhLMTc1b+f6wMTZToFAmqC7e8UHGFncnVlbmJh
 QHJlZGhhdC5jb20ACgkQ1b+f6wMTZTql+A/+OLEPtizpJ6k/uqk7+I63D0xYakK5
 GMWCPPf5C9fernW57cF/p0TfRRDUKqKOrb+mKkJuPBMSfEZIldKOmYQx6U/kvXIF
 6hdtRfAvVBjIs1aY+P/dliRXfzvO1HVamPsu7dx7NdTnozJz2tv/spNQ2cZV5tQD
 2t6VovUzje+U7x7Qsz3EhN/po1aNBlXlzWtvF/r7uwAtiYfXaknJkr8cjo202w0Y
 fAQ5KRZA5EiN1Fc1d6V7ehYyzv31osmaQA9Bsf7NVlpnBPG1LyibYWzFF4zg1pDo
 9JU4tcD9IpIALwYkGFFcG1qutjq0E2QKqeYgaIau94ezQnTY+q1zQQDiixyoHxHu
 2AHMIcbUzEKHib+pYWtSy5GLgeTI59+hxFe9tOERhjHGDW8fYbqQTV969S84bYTm
 +Ar04VsbToMdjYSh5I+FPLQbKwfetKjDIlKSYeZzMOfGEqpJtoU9lB8WbvP/2LcQ
 c5hJfJcV7JWW9j4YZZIZuwzZv93CstBvFubKpktMw0x9utGc6a7M+f9eADYnd3JH
 l7yb+dyjUeYeIqEbALsfnxicuoiYfVKNVxh/zdz4TJEYDH1l/CyBIirLsSAVTAJl
 ruCT13akq5e+EXGOztcIXyU39l/ajJA0v58n9gzEzMakTwpyCTwx7WcooILf1aem
 P1QRnAoud8FoAYk=
 =U72j
 -----END PGP SIGNATURE-----

Merge tag 'gfs2-for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2

Pull gfs2 updates from Andreas Gruenbacher:

 - Don't cache unreferenced glocks: when a glock is no longer referenced
   (for example, because the inode it protects is evicted), it is now
   released as soon as possible instead of leaving it around until
   memory pressure or an unmount forces it out.

   For some workloads, this saves a lot of memory and speeds up unmounts
   significantly.

 - Harden gfs2_glock_hold() by making sure the caller holds a reference
   and fix a related race in checking for the liveliness of glocks
   between gdlm_bast() and gfs2_glock_cb().

* tag 'gfs2-for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2:
  gfs2: harden gfs2_glock_hold
  gfs2: Remove the glock lru list and shrinker
  gfs2: Skip dlm unlocks earlier
  gfs2: Don't cache unreferenced glocks
  gfs2: Enable automatic glock hash table shrinking
This commit is contained in:
Linus Torvalds 2026-08-17 17:03:34 -07:00
commit 4bb187d6f3
9 changed files with 35 additions and 221 deletions

View File

@ -66,9 +66,6 @@ static void request_demote(struct gfs2_glock *gl, unsigned int state,
unsigned long delay, bool remote);
static struct dentry *gfs2_root;
static LIST_HEAD(lru_list);
static atomic_t lru_count = ATOMIC_INIT(0);
static DEFINE_SPINLOCK(lru_lock);
#define GFS2_GL_HASH_SHIFT 15
#define GFS2_GL_HASH_SIZE BIT(GFS2_GL_HASH_SHIFT)
@ -78,6 +75,7 @@ static const struct rhashtable_params ht_parms = {
.key_len = offsetofend(struct lm_lockname, ln_type),
.key_offset = offsetof(struct gfs2_glock, gl_name),
.head_offset = offsetof(struct gfs2_glock, gl_node),
.automatic_shrinking = true,
};
static struct rhashtable gl_hash_table;
@ -157,9 +155,9 @@ void gfs2_glock_free(struct gfs2_glock *gl) {
void gfs2_glock_free_later(struct gfs2_glock *gl) {
struct gfs2_sbd *sdp = glock_sbd(gl);
spin_lock(&lru_lock);
list_add(&gl->gl_lru, &sdp->sd_dead_glocks);
spin_unlock(&lru_lock);
spin_lock(&sdp->sd_dead_lock);
list_add(&gl->gl_dead, &sdp->sd_dead_glocks);
spin_unlock(&sdp->sd_dead_lock);
if (atomic_dec_and_test(&sdp->sd_glock_disposal))
wake_up(&sdp->sd_kill_wait);
}
@ -171,8 +169,8 @@ static void gfs2_free_dead_glocks(struct gfs2_sbd *sdp)
while(!list_empty(list)) {
struct gfs2_glock *gl;
gl = list_first_entry(list, struct gfs2_glock, gl_lru);
list_del_init(&gl->gl_lru);
gl = list_first_entry(list, struct gfs2_glock, gl_dead);
list_del(&gl->gl_dead);
__gfs2_glock_free(gl);
}
}
@ -185,35 +183,11 @@ static void gfs2_free_dead_glocks(struct gfs2_sbd *sdp)
struct gfs2_glock *gfs2_glock_hold(struct gfs2_glock *gl)
{
if (!lockref_get_not_dead(&gl->gl_lockref))
if (!lockref_get_not_zero(&gl->gl_lockref))
GLOCK_BUG_ON(gl, 1);
return gl;
}
static void gfs2_glock_add_to_lru(struct gfs2_glock *gl)
{
spin_lock(&lru_lock);
list_move_tail(&gl->gl_lru, &lru_list);
if (!test_bit(GLF_LRU, &gl->gl_flags)) {
set_bit(GLF_LRU, &gl->gl_flags);
atomic_inc(&lru_count);
}
spin_unlock(&lru_lock);
}
static void gfs2_glock_remove_from_lru(struct gfs2_glock *gl)
{
spin_lock(&lru_lock);
if (test_bit(GLF_LRU, &gl->gl_flags)) {
list_del_init(&gl->gl_lru);
atomic_dec(&lru_count);
clear_bit(GLF_LRU, &gl->gl_flags);
}
spin_unlock(&lru_lock);
}
/*
* Enqueue the glock on the work queue. Passes one glock reference on to the
* work queue.
@ -240,7 +214,6 @@ static void __gfs2_glock_put(struct gfs2_glock *gl)
lockref_mark_dead(&gl->gl_lockref);
spin_unlock(&gl->gl_lockref.lock);
gfs2_glock_remove_from_lru(gl);
GLOCK_BUG_ON(gl, !list_empty(&gl->gl_holders));
if (mapping) {
truncate_inode_pages_final(mapping);
@ -257,8 +230,8 @@ static bool __gfs2_glock_put_or_lock(struct gfs2_glock *gl)
return true;
GLOCK_BUG_ON(gl, gl->gl_lockref.count != 1);
if (gl->gl_state != LM_ST_UNLOCKED) {
gl->gl_lockref.count--;
gfs2_glock_add_to_lru(gl);
request_demote(gl, LM_ST_UNLOCKED, 0, false);
gfs2_glock_queue_work(gl, 0);
spin_unlock(&gl->gl_lockref.lock);
return true;
}
@ -982,16 +955,19 @@ static void delete_work_func(struct work_struct *work)
static void glock_work_func(struct work_struct *work)
{
unsigned long delay = 0;
struct gfs2_glock *gl = container_of(work, struct gfs2_glock, gl_work.work);
unsigned int drop_refs = 1;
unsigned int drop_refs;
unsigned long delay;
spin_lock(&gl->gl_lockref.lock);
again:
drop_refs = 1;
if (test_bit(GLF_HAVE_REPLY, &gl->gl_flags)) {
clear_bit(GLF_HAVE_REPLY, &gl->gl_flags);
finish_xmote(gl, gl->gl_reply);
drop_refs++;
}
delay = 0;
if (test_bit(GLF_PENDING_DEMOTE, &gl->gl_flags) &&
gl->gl_state != LM_ST_UNLOCKED &&
gl->gl_demote_state != LM_ST_EXCLUSIVE) {
@ -1019,11 +995,13 @@ static void glock_work_func(struct work_struct *work)
GLOCK_BUG_ON(gl, gl->gl_lockref.count < drop_refs);
gl->gl_lockref.count -= drop_refs;
if (!gl->gl_lockref.count) {
if (gl->gl_state == LM_ST_UNLOCKED) {
__gfs2_glock_put(gl);
return;
if (gl->gl_state != LM_ST_UNLOCKED) {
gl->gl_lockref.count++;
request_demote(gl, LM_ST_UNLOCKED, 0, false);
goto again;
}
gfs2_glock_add_to_lru(gl);
__gfs2_glock_put(gl);
return;
}
spin_unlock(&gl->gl_lockref.lock);
}
@ -1059,8 +1037,6 @@ static struct gfs2_glock *find_insert_glock(struct lm_lockname *name,
out:
rcu_read_unlock();
finish_wait(wq, &wait.wait);
if (gl)
gfs2_glock_remove_from_lru(gl);
return gl;
}
@ -1793,7 +1769,9 @@ void gfs2_glock_cb(struct gfs2_glock *gl, unsigned int state)
{
unsigned long delay = 0;
gfs2_glock_hold(gl);
if (!lockref_get_not_dead(&gl->gl_lockref))
return;
spin_lock(&gl->gl_lockref.lock);
if (!list_empty(&gl->gl_holders) &&
glock_type(gl) == LM_TYPE_INODE) {
@ -1873,125 +1851,6 @@ void gfs2_glock_complete(struct gfs2_glock *gl, int ret)
spin_unlock(&gl->gl_lockref.lock);
}
static int glock_cmp(void *priv, const struct list_head *a,
const struct list_head *b)
{
struct gfs2_glock *gla, *glb;
gla = list_entry(a, struct gfs2_glock, gl_lru);
glb = list_entry(b, struct gfs2_glock, gl_lru);
if (glock_number(gla) > glock_number(glb))
return 1;
if (glock_number(gla) < glock_number(glb))
return -1;
return 0;
}
static bool can_free_glock(struct gfs2_glock *gl)
{
struct gfs2_sbd *sdp = glock_sbd(gl);
return !test_bit(GLF_LOCK, &gl->gl_flags) &&
!gl->gl_lockref.count &&
(!test_bit(GLF_LFLUSH, &gl->gl_flags) ||
test_bit(SDF_KILL, &sdp->sd_flags));
}
/**
* gfs2_dispose_glock_lru - Demote a list of glocks
* @list: The list to dispose of
*
* Disposing of glocks may involve disk accesses, so that here we sort
* the glocks by number (i.e. disk location of the inodes) so that if
* there are any such accesses, they'll be sent in order (mostly).
*
* Must be called under the lru_lock, but may drop and retake this
* lock. While the lru_lock is dropped, entries may vanish from the
* list, but no new entries will appear on the list (since it is
* private)
*/
static unsigned long gfs2_dispose_glock_lru(struct list_head *list)
__releases(&lru_lock)
__acquires(&lru_lock)
{
struct gfs2_glock *gl;
unsigned long freed = 0;
list_sort(NULL, list, glock_cmp);
while(!list_empty(list)) {
gl = list_first_entry(list, struct gfs2_glock, gl_lru);
if (!spin_trylock(&gl->gl_lockref.lock)) {
add_back_to_lru:
list_move(&gl->gl_lru, &lru_list);
continue;
}
if (!can_free_glock(gl)) {
spin_unlock(&gl->gl_lockref.lock);
goto add_back_to_lru;
}
list_del_init(&gl->gl_lru);
atomic_dec(&lru_count);
clear_bit(GLF_LRU, &gl->gl_flags);
freed++;
gl->gl_lockref.count++;
if (gl->gl_state != LM_ST_UNLOCKED)
request_demote(gl, LM_ST_UNLOCKED, 0, false);
gfs2_glock_queue_work(gl, 0);
spin_unlock(&gl->gl_lockref.lock);
cond_resched_lock(&lru_lock);
}
return freed;
}
/**
* gfs2_scan_glock_lru - Scan the LRU looking for locks to demote
* @nr: The number of entries to scan
*
* This function selects the entries on the LRU which are able to
* be demoted, and then kicks off the process by calling
* gfs2_dispose_glock_lru() above.
*/
static unsigned long gfs2_scan_glock_lru(unsigned long nr)
{
struct gfs2_glock *gl, *next;
LIST_HEAD(dispose);
unsigned long freed = 0;
spin_lock(&lru_lock);
list_for_each_entry_safe(gl, next, &lru_list, gl_lru) {
if (!nr--)
break;
if (can_free_glock(gl))
list_move(&gl->gl_lru, &dispose);
}
if (!list_empty(&dispose))
freed = gfs2_dispose_glock_lru(&dispose);
spin_unlock(&lru_lock);
return freed;
}
static unsigned long gfs2_glock_shrink_scan(struct shrinker *shrink,
struct shrink_control *sc)
{
if (!(sc->gfp_mask & __GFP_FS))
return SHRINK_STOP;
return gfs2_scan_glock_lru(sc->nr_to_scan);
}
static unsigned long gfs2_glock_shrink_count(struct shrinker *shrink,
struct shrink_control *sc)
{
return vfs_pressure_ratio(atomic_read(&lru_count));
}
static struct shrinker *glock_shrinker;
/**
* glock_hash_walk - Call a function for glock in a hash bucket
* @examiner: the function
@ -2062,33 +1921,12 @@ static void thaw_glock(struct gfs2_glock *gl)
if (!lockref_get_not_dead(&gl->gl_lockref))
return;
gfs2_glock_remove_from_lru(gl);
spin_lock(&gl->gl_lockref.lock);
set_bit(GLF_HAVE_REPLY, &gl->gl_flags);
gfs2_glock_queue_work(gl, 0);
spin_unlock(&gl->gl_lockref.lock);
}
/**
* clear_glock - look at a glock and see if we can free it from glock cache
* @gl: the glock to look at
*
*/
static void clear_glock(struct gfs2_glock *gl)
{
gfs2_glock_remove_from_lru(gl);
spin_lock(&gl->gl_lockref.lock);
if (!lockref_is_dead(&gl->gl_lockref)) {
gl->gl_lockref.count++;
if (gl->gl_state != LM_ST_UNLOCKED)
request_demote(gl, LM_ST_UNLOCKED, 0, false);
gfs2_glock_queue_work(gl, 0);
}
spin_unlock(&gl->gl_lockref.lock);
}
/**
* gfs2_glock_thaw - Thaw any frozen glocks
* @sdp: The super block
@ -2135,22 +1973,17 @@ void gfs2_withdraw_glocks(struct gfs2_sbd *sdp)
}
/**
* gfs2_gl_hash_clear - Empty out the glock hash table
* gfs2_wait_glocks - Wait for the remaining glocks to go away
* @sdp: the filesystem
*
* Called when unmounting the filesystem.
*/
void gfs2_gl_hash_clear(struct gfs2_sbd *sdp)
void gfs2_wait_glocks(struct gfs2_sbd *sdp)
{
unsigned long start = jiffies;
bool timed_out = false;
set_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags);
flush_workqueue(sdp->sd_glock_wq);
glock_hash_walk(clear_glock, sdp);
flush_workqueue(sdp->sd_glock_wq);
while (!timed_out) {
wait_event_timeout(sdp->sd_kill_wait,
!atomic_read(&sdp->sd_glock_disposal),
@ -2273,8 +2106,6 @@ static const char *gflags2str(char *buf, const struct gfs2_glock *gl)
*p++ = 'F';
if (!list_empty(&gl->gl_holders))
*p++ = 'q';
if (test_bit(GLF_LRU, gflags))
*p++ = 'L';
if (gl->gl_object)
*p++ = 'o';
if (test_bit(GLF_BLOCKING, gflags))
@ -2434,17 +2265,6 @@ int __init gfs2_glock_init(void)
if (ret < 0)
return ret;
glock_shrinker = shrinker_alloc(0, "gfs2-glock");
if (!glock_shrinker) {
rhashtable_destroy(&gl_hash_table);
return -ENOMEM;
}
glock_shrinker->count_objects = gfs2_glock_shrink_count;
glock_shrinker->scan_objects = gfs2_glock_shrink_scan;
shrinker_register(glock_shrinker);
for (i = 0; i < GLOCK_WAIT_TABLE_SIZE; i++)
init_waitqueue_head(glock_wait_table + i);
@ -2453,7 +2273,6 @@ int __init gfs2_glock_init(void)
void gfs2_glock_exit(void)
{
shrinker_free(glock_shrinker);
rhashtable_destroy(&gl_hash_table);
}

View File

@ -263,7 +263,7 @@ bool gfs2_queue_try_to_evict(struct gfs2_glock *gl);
bool gfs2_queue_verify_delete(struct gfs2_glock *gl, bool later);
void gfs2_cancel_delete_work(struct gfs2_glock *gl);
void gfs2_flush_delete_work(struct gfs2_sbd *sdp);
void gfs2_gl_hash_clear(struct gfs2_sbd *sdp);
void gfs2_wait_glocks(struct gfs2_sbd *sdp);
void gfs2_withdraw_glocks(struct gfs2_sbd *sdp);
void gfs2_glock_thaw(struct gfs2_sbd *sdp);
void gfs2_glock_free(struct gfs2_glock *gl);

View File

@ -321,7 +321,6 @@ enum {
GLF_INITIAL = 10,
GLF_HAVE_FROZEN_REPLY = 11,
GLF_INSTANTIATE_IN_PROG = 12, /* instantiate happening now */
GLF_LRU = 13,
GLF_OBJECT = 14, /* Used only for tracing */
GLF_BLOCKING = 15,
GLF_TRY_TO_EVICT = 17, /* iopen glocks only */
@ -355,7 +354,7 @@ struct gfs2_glock {
unsigned long gl_tchange;
void *gl_object;
struct list_head gl_lru;
struct list_head gl_dead;
struct list_head gl_ail_list;
atomic_t gl_ail_count;
atomic_t gl_revokes;
@ -601,7 +600,6 @@ enum {
SDF_DEMOTE = 5,
SDF_NOJOURNALID = 6,
SDF_RORECOVERY = 7, /* read only recovery */
SDF_SKIP_DLM_UNLOCK = 8,
SDF_FORCE_AIL_FLUSH = 9,
SDF_FREEZE_INITIATOR = 10,
SDF_KILL = 15,
@ -834,6 +832,9 @@ struct gfs2_sbd {
struct list_head sd_ail1_list;
struct list_head sd_ail2_list;
/* glocks */
spinlock_t sd_dead_lock;
/* For quiescing the filesystem */
struct gfs2_holder sd_freeze_gh;
struct mutex sd_freeze_mutex;

View File

@ -182,9 +182,6 @@ static void gdlm_bast(void *arg, int mode)
{
struct gfs2_glock *gl = arg;
if (lockref_is_dead(&gl->gl_lockref))
return;
switch (mode) {
case DLM_LOCK_EX:
gfs2_glock_cb(gl, LM_ST_UNLOCKED);
@ -347,7 +344,7 @@ static void gdlm_put_lock(struct gfs2_glock *gl)
* DLM_LOCK_PW mode, the lock value block (LVB) would be lost.
*/
if (test_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags) &&
if (test_bit(SDF_KILL, &sdp->sd_flags) &&
(!gl->gl_lksb.sb_lvbptr || gl->gl_state != LM_ST_EXCLUSIVE)) {
gfs2_glock_free_later(gl);
return;

View File

@ -52,7 +52,6 @@ static void gfs2_init_glock_once(void *foo)
struct gfs2_glock *gl = foo;
INIT_LIST_HEAD(&gl->gl_holders);
INIT_LIST_HEAD(&gl->gl_lru);
INIT_LIST_HEAD(&gl->gl_ail_list);
atomic_set(&gl->gl_ail_count, 0);
atomic_set(&gl->gl_revokes, 0);

View File

@ -123,6 +123,8 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb)
INIT_LIST_HEAD(&sdp->sd_ail1_list);
INIT_LIST_HEAD(&sdp->sd_ail2_list);
spin_lock_init(&sdp->sd_dead_lock);
init_rwsem(&sdp->sd_log_flush_lock);
atomic_set(&sdp->sd_log_in_flight, 0);
init_waitqueue_head(&sdp->sd_log_flush_wait);
@ -1300,7 +1302,6 @@ static int gfs2_fill_super(struct super_block *sb, struct fs_context *fc)
init_locking(sdp, &mount_gh, UNDO);
fail_lm:
complete_all(&sdp->sd_journal_ready);
gfs2_gl_hash_clear(sdp);
gfs2_lm_unmount(sdp);
fail_debug:
gfs2_delete_debugfs_file(sdp);
@ -1783,6 +1784,7 @@ static void gfs2_kill_sb(struct super_block *sb)
sdp->sd_master_dir = NULL;
shrink_dcache_sb(sb);
set_bit(SDF_KILL, &sdp->sd_flags);
gfs2_evict_inodes(sb);
/*
@ -1790,7 +1792,6 @@ static void gfs2_kill_sb(struct super_block *sb)
* destroy_workqueue()) to ensure that any delete work that
* may be running will also see the SDF_KILL flag.
*/
set_bit(SDF_KILL, &sdp->sd_flags);
gfs2_flush_delete_work(sdp);
destroy_workqueue(sdp->sd_delete_wq);

View File

@ -638,7 +638,7 @@ static void gfs2_put_super(struct super_block *sb)
gfs2_clear_rgrpd(sdp);
gfs2_jindex_free(sdp);
/* Take apart glock structures and buffer lists */
gfs2_gl_hash_clear(sdp);
gfs2_wait_glocks(sdp);
iput(sdp->sd_inode);
gfs2_delete_debugfs_file(sdp);

View File

@ -80,7 +80,6 @@ static ssize_t status_show(struct gfs2_sbd *sdp, char *buf)
"No Journal ID: %d\n"
"Mounted RO: %d\n"
"RO Recovery: %d\n"
"Skip DLM Unlock: %d\n"
"Force AIL Flush: %d\n"
"FS Freeze Initiator: %d\n"
"FS Frozen: %d\n"
@ -109,7 +108,6 @@ static ssize_t status_show(struct gfs2_sbd *sdp, char *buf)
test_bit(SDF_NOJOURNALID, &f),
(sb_rdonly(sdp->sd_vfs) ? 1 : 0),
test_bit(SDF_RORECOVERY, &f),
test_bit(SDF_SKIP_DLM_UNLOCK, &f),
test_bit(SDF_FORCE_AIL_FLUSH, &f),
test_bit(SDF_FREEZE_INITIATOR, &f),
test_bit(SDF_FROZEN, &f),

View File

@ -56,7 +56,6 @@
{(1UL << GLF_HAVE_REPLY), "r" }, \
{(1UL << GLF_INITIAL), "a" }, \
{(1UL << GLF_HAVE_FROZEN_REPLY), "F" }, \
{(1UL << GLF_LRU), "L" }, \
{(1UL << GLF_OBJECT), "o" }, \
{(1UL << GLF_BLOCKING), "b" }, \
{(1UL << GLF_INSTANTIATE_NEEDED), "n" }, \