Skip to contentSkip to conversation

ext4: Fix ENOSPC error, improve mballoc dbg, other cleanups

RFC Superseded Ritesh Harjani 3 participants 26 messages May 1, 2020 – May 5, 2020

Subsystems (3)

v1 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

cover ext4: Fix ENOSPC error, improve mballoc dbg, other cleanups

Hello All,

v2 -> v3:
v3 changes the code design to fix ENOSPC error. This patch uses the percpu discard pa seq counter (which was as discussed with Jan & Aneesh). Patch-2 commit msg describes both the problem and the new algorithm in great detail. Rest of the patches are mostly either refactoring, code cleanups or debug logs improvements.

Posting this for early review comments.
For now I have done some basic testing on this patch to test for ENOSPC reported errors, using a smaller filesystem (~240MB, 64K bs) a. Tested multi-thread file writes which only allocates group PAs. b. Tested multi-thread file writes which only allocates inode PAs. c. Tested multi-thread file writes doing combination of both of the above.

[May Not be Ready for Merge yet, until below are properly discussed]
==================================================================

1. There is a query asked in Patch-2 commit msg itself, regarding rcu_barrier() usage.

2. AFAICT, even if we reduce the PA size based on available FS size to avoid this ENOSPC error, this issue could still potentially happen since the race has mostly to do with 1st thread freeing up all the PAs while 2nd thread returning 0 as freed (since PA list was empty). Hence 2nd thread fails with ENOSPC even though there were free blocks freed by 1st thread.

3. I would like to know if there is any stress-ng test case or any other use case which measures multi-thread performance of write while FS is close to ENOSPC? If yes - instead of regressing this later, I would like to know such test case so that it can be tested at my end while we are still at it.

4. I do see that with 64K blocksize the performance of multi-thread < 1 MB file size writes becomes very slow. But without this patch, it anyways fails with ENOSPC error. On doing below the performance does improve close to 5x. echo 32 > /sys/fs/ext4/loop3/mb_group_prealloc
I was thinking instead of 512 blocks as default value for 'MB_DEFAULT_GROUP_PREALLOC', we could make it 64K as the default size and decide the no. of blocks based on the blocksize. But I haven't got to it yet. But thought to capture it in this email though. We can get to it after these patches.

5. Started fstests testing. Will let you know the results soon.

[RFCv2]: https://patchwork.ozlabs.org/project/linux-ext4/patch/533ac1f5b19c520b08f8c99aec5baf8729185714.1586954511.git.riteshh@linux.ibm.com/
- Problems with v2 are captured in Patch-2 commit msg itself.

Ritesh Harjani (20):
ext4: mballoc: Refactor ext4_mb_discard_preallocations()
ext4: Introduce percpu seq counter for freeing blocks(PA) to avoid
ENOSPC err
ext4: mballoc: Do print bb_free info even when it is 0
ext4: mballoc: Refactor ext4_mb_show_ac()
ext4: mballoc: Add more mb_debug() msgs
ext4: mballoc: Correct the mb_debug() format specifier for pa_len var
ext4: mballoc: Fix few other format specifier in mb_debug()
ext4: mballoc: Simplify error handling in ext4_init_mballoc()
ext4: mballoc: Make ext4_mb_use_preallocated() return type as bool
ext4: mballoc: Remove EXT4_MB_HINT_GOAL_ONLY and it's related code
ext4: mballoc: Refactor code inside DOUBLE_CHECK into separate
function
ext4: mballoc: Fix possible NULL ptr dereference from mb_cmp_bitmaps()
ext4: mballoc: Don't BUG if kmalloc or read blk bitmap fail for
DOUBLE_CHECK
ext4: balloc: Use task_pid_nr() helper
ext4: Use BIT() macro for BH_** state bits
ext4: Improve ext_debug() msg in case of block allocation failure
ext4: Replace EXT_DEBUG with __maybe_unused in
ext4_ext_handle_unwritten_extents()
ext4: mballoc: Make mb_debug() implementation to use pr_debug()
ext4: Make ext_debug() implementation to use pr_debug()
ext4: Add process name and pid in ext4_msg()

 fs/ext4/Kconfig             |   3 +-
 fs/ext4/balloc.c            |   5 +-
 fs/ext4/ext4.h              |  38 ++--
 fs/ext4/extents.c           | 150 ++++++++--------
 fs/ext4/inode.c             |  15 +-
 fs/ext4/mballoc.c           | 347 +++++++++++++++++++++++-------------
 fs/ext4/mballoc.h           |  16 +-
 fs/ext4/super.c             |   3 +-
 include/trace/events/ext4.h |   1 -
 9 files changed, 335 insertions(+), 243 deletions(-)
Signature · 1 line
2.21.0
#1 lore raw

RFC 1/20 ext4: mballoc: Refactor ext4_mb_discard_preallocations()

Implement ext4_mb_discard_preallocations_should_retry() which we will need in later patches to add more logic like check for sequence number match to see if we should retry for block allocation or not.

There should be no functionality change in this patch.

---
 fs/ext4/mballoc.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)
fs/ext4/mballoc.c +12 -3
index 30d5d97548c4..a742e51e33b8 100644
@@ -4486,6 +4486,17 @@ static int ext4_mb_discard_preallocations(struct super_block *sb, int needed)
 	return freed;
 }
 
+static bool ext4_mb_discard_preallocations_should_retry(struct super_block *sb,
+			struct ext4_allocation_context *ac)
+{
+	int freed;
+
+	freed = ext4_mb_discard_preallocations(sb, ac->ac_o_ex.fe_len);
+	if (freed)
+		return true;
+	return false;
+}
+
 /*
  * Main entry point into mballoc to allocate blocks
  * it tries to use preallocation first, then falls back
@@ -4494,7 +4505,6 @@ static int ext4_mb_discard_preallocations(struct super_block *sb, int needed)
 ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 				struct ext4_allocation_request *ar, int *errp)
 {
-	int freed;
 	struct ext4_allocation_context *ac = NULL;
 	struct ext4_sb_info *sbi;
 	struct super_block *sb;
@@ -4593,8 +4603,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 			ar->len = ac->ac_b_ex.fe_len;
 		}
 	} else {
-		freed  = ext4_mb_discard_preallocations(sb, ac->ac_o_ex.fe_len);
-		if (freed)
+		if (ext4_mb_discard_preallocations_should_retry(sb, ac))
 			goto repeat;
 		*errp = -ENOSPC;
 	}
Signature · 1 line
2.21.0
#2 lore raw

RFC 2/20 ext4: Introduce percpu seq counter for freeing blocks(PA) to avoid ENOSPC err

In reply to Ritesh Harjani

There could be a race in function ext4_mb_discard_group_preallocations() where the 1st thread may iterate through group's bb_prealloc_list and remove all the PAs and add to function's local list head. Now if the 2nd thread comes in to discard the group preallocations, it will see that the group->bb_prealloc_list is empty and will return 0.

Consider for a case where we have less number of groups (for e.g. just group 0),
this may even return an -ENOSPC error from ext4_mb_new_blocks() (where we call for ext4_mb_discard_group_preallocations()). But that is wrong, since 2nd thread should have waited for 1st thread to release all the PAs and should have retried for allocation. Since 1st thread was anyway going to discard the PAs.

The algorithm using this percpu seq counter goes below:
1. We sample the percpu discard_pa_seq counter before trying for block allocation in ext4_mb_new_blocks().
2. We increment this percpu discard_pa_seq counter when we succeed in allocating blocks and hence while adding the remaining blocks in group's bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
3. We also increment this percpu seq counter when we successfully identify that the bb_prealloc_list is not empty and hence proceed for discarding of those PAs inside ext4_mb_discard_group_preallocations().

Now to make sure that the regular fast path of block allocation is not affected, as a small optimization we only sample the percpu seq counter on that cpu. Only when the block allocation fails and when freed blocks found were 0, that is when we sample percpu seq counter for all cpus using below function ext4_get_discard_pa_seq_sum(). This happens after making sure that all the PAs on grp->bb_prealloc_list got freed.

TO CHECK: Though in here rcu_barrier only happens in ENOSPC path.
=================================================================
On rcu_barrier() - How expensive it can be?
Does that mean that every thread who is coming and waiting on rcu_barrier() will actually check whether call_rcu() has completed by checking that on every cpu? So will this be a O(n*m) operation? (n = no. of threads, m = no. of cpus).
Or are there some sort of optimization in using rcu_barrier()?

---
Note: The other method [1] also used to check for grp->bb_free next time if we couldn't discard anything. But it had below concerns due to which we cannot use that approach.
1. But one suspicion with that was that if grp->bb_free is non-zero for some reason (not sure), then we may result in that loop indefinitely but still won't be able to satisfy any request.
2. To check for grp->bb_free all threads were to wait on grp's spin_lock which might result in increased cpu usage.
3. In case of group's PA allocation (i.e. when file's size is < 1MB for 64K blocksize), there is still a case where ENOSPC could be returned. This happens when the grp->bb_free is set to 0 but those blocks are actually not yet added to PA. Yes, this could actually happen since reducing grp->bb_free and adding those extra blocks in bb_prealloc_list are not done atomically. Hence the race.

[1]: https://patchwork.ozlabs.org/project/linux-ext4/patch/533ac1f5b19c520b08f8c99aec5baf8729185714.1586954511.git.riteshh@linux.ibm.com/

---
 fs/ext4/mballoc.c | 65 +++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 60 insertions(+), 5 deletions(-)
fs/ext4/mballoc.c +60 -5
index a742e51e33b8..6bb08bb3c0ce 100644
@@ -357,6 +357,35 @@ static void ext4_mb_generate_from_pa(struct super_block *sb, void *bitmap,
 static void ext4_mb_generate_from_freelist(struct super_block *sb, void *bitmap,
 						ext4_group_t group);
 
+/*
+ * The algorithm using this percpu seq counter goes below:
+ * 1. We sample the percpu discard_pa_seq counter before trying for block
+ *    allocation in ext4_mb_new_blocks().
+ * 2. We increment this percpu discard_pa_seq counter when we succeed in
+ *    allocating blocks and hence while adding the remaining blocks in group's
+ *    bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
+ * 3. We also increment this percpu seq counter when we successfully identify
+ *    that the bb_prealloc_list is not empty and hence proceed for discarding
+ *    of those PAs inside ext4_mb_discard_group_preallocations().
+ *
+ * Now to make sure that the regular fast path of block allocation is not
+ * affected, as a small optimization we only sample the percpu seq counter
+ * on that cpu. Only when the block allocation fails and when freed blocks
+ * found were 0, that is when we sample percpu seq counter for all cpus using
+ * below function ext4_get_discard_pa_seq_sum(). This happens after making
+ * sure that all the PAs on grp->bb_prealloc_list got freed.
+ */
+DEFINE_PER_CPU(u64, discard_pa_seq);
+static inline u64 ext4_get_discard_pa_seq_sum(void)
+{
+	int __cpu;
+	u64 __seq = 0;
+
+	for_each_possible_cpu(__cpu)
+		__seq += per_cpu(discard_pa_seq, __cpu);
+	return __seq;
+}
+
 static inline void *mb_correct_addr_and_bit(int *bit, void *addr)
 {
 #if BITS_PER_LONG == 64
@@ -3730,6 +3759,7 @@ ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
 	pa->pa_inode = ac->ac_inode;
 
 	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
 	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
 	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
 
@@ -3791,6 +3821,7 @@ ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
 	pa->pa_inode = NULL;
 
 	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
 	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
 	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
 
@@ -3943,6 +3974,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
 	INIT_LIST_HEAD(&list);
 repeat:
 	ext4_lock_group(sb, group);
+	this_cpu_inc(discard_pa_seq);
 	list_for_each_entry_safe(pa, tmp,
 				&grp->bb_prealloc_list, pa_group_list) {
 		spin_lock(&pa->pa_lock);
@@ -4487,14 +4519,35 @@ static int ext4_mb_discard_preallocations(struct super_block *sb, int needed)
 }
 
 static bool ext4_mb_discard_preallocations_should_retry(struct super_block *sb,
-			struct ext4_allocation_context *ac)
+			struct ext4_allocation_context *ac, u64 *seq)
 {
 	int freed;
+	u64 seq_retry = 0;
+	bool ret = false;
 
 	freed = ext4_mb_discard_preallocations(sb, ac->ac_o_ex.fe_len);
-	if (freed)
-		return true;
-	return false;
+	if (freed) {
+		ret = true;
+		goto out_dbg;
+	}
+	/*
+	 * Unless it is ensured that PAs are actually freed, we may hit
+	 * a ENOSPC error since the next time seq may match while the PA blocks
+	 * are still getting freed in ext4_mb_release_inode/group_pa().
+	 * So, rcu_barrier() here is to make sure that any call_rcu queued in
+	 * ext4_mb_discard_group_preallocations() is completed before we
+	 * proceed further to retry for block allocation.
+	 */
+	rcu_barrier();
+	seq_retry = ext4_get_discard_pa_seq_sum();
+	if (seq_retry != *seq) {
+		*seq = seq_retry;
+		ret = true;
+	}
+
+out_dbg:
+	mb_debug(1, "freed %d, retry ? %s\n", freed, ret ? "yes" : "no");
+	return ret;
 }
 
 /*
@@ -4511,6 +4564,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 	ext4_fsblk_t block = 0;
 	unsigned int inquota = 0;
 	unsigned int reserv_clstrs = 0;
+	u64 seq;
 
 	might_sleep();
 	sb = ar->inode->i_sb;
@@ -4572,6 +4626,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 	}
 
 	ac->ac_op = EXT4_MB_HISTORY_PREALLOC;
+	seq = *this_cpu_ptr(&discard_pa_seq);
 	if (!ext4_mb_use_preallocated(ac)) {
 		ac->ac_op = EXT4_MB_HISTORY_ALLOC;
 		ext4_mb_normalize_request(ac, ar);
@@ -4603,7 +4658,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 			ar->len = ac->ac_b_ex.fe_len;
 		}
 	} else {
-		if (ext4_mb_discard_preallocations_should_retry(sb, ac))
+		if (ext4_mb_discard_preallocations_should_retry(sb, ac, &seq))
 			goto repeat;
 		*errp = -ENOSPC;
 	}
Signature · 1 line
2.21.0
#3 lore raw

Re: [RFC 02/20] ext4: Introduce percpu seq counter for freeing blocks(PA) to avoid ENOSPC err

[if your patch is applied to the wrong git tree, please drop us a note to help improve the system. BTW, we also suggest to use '--base' option to specify the base tree in git format-patch, please…

Hi Ritesh,

[FYI, it's a private test report for your RFC patch.]
[auto build test WARNING on ext4/dev]
[also build test WARNING on tip/perf/core ext3/for_next v5.7-rc3 next-20200501] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system. BTW, we also suggest to use '--base' option to specify the base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Ritesh-Harjani/ext4-Fix-ENOSPC-error-improve-mballoc-dbg-other-cleanups/20200501-155110
base:   https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git dev
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.1-191-gc51a0382-dirty
        make ARCH=x86_64 allmodconfig
        make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag as appropriate

sparse warnings: (new ones prefixed by >>)

>> fs/ext4/mballoc.c:378:1: sparse: sparse: symbol '__pcpu_scope_discard_pa_seq' was not declared. Should it be static?

Please review and possibly fold the followup patch.

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

#4 lore raw

[RFC PATCH] ext4: __pcpu_scope_discard_pa_seq can be static

In reply to Ritesh Harjani
Signed-off-by: kbuild test robot <[email protected]>
---
 mballoc.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/ext4/mballoc.c +1 -1
index 6bb08bb3c0cee..462ddad104315 100644
@@ -375,7 +375,7 @@ static void ext4_mb_generate_from_freelist(struct super_block *sb, void *bitmap,
  * below function ext4_get_discard_pa_seq_sum(). This happens after making
  * sure that all the PAs on grp->bb_prealloc_list got freed.
  */
-DEFINE_PER_CPU(u64, discard_pa_seq);
+static DEFINE_PER_CPU(u64, discard_pa_seq);
 static inline u64 ext4_get_discard_pa_seq_sum(void)
 {
 	int __cpu;
#5 lore raw

Re: [RFC 02/20] ext4: Introduce percpu seq counter for freeing blocks(PA) to avoid ENOSPC err

In reply to Ritesh Harjani
Quoted 31 lines · Ritesh Harjani

There could be a race in function ext4_mb_discard_group_preallocations() where the 1st thread may iterate through group's bb_prealloc_list and remove all the PAs and add to function's local list head. Now if the 2nd thread comes in to discard the group preallocations, it will see that the group->bb_prealloc_list is empty and will return 0.

Consider for a case where we have less number of groups (for e.g. just group 0),
this may even return an -ENOSPC error from ext4_mb_new_blocks() (where we call for ext4_mb_discard_group_preallocations()). But that is wrong, since 2nd thread should have waited for 1st thread to release all the PAs and should have retried for allocation. Since 1st thread was anyway going to discard the PAs.

The algorithm using this percpu seq counter goes below:
1. We sample the percpu discard_pa_seq counter before trying for block allocation in ext4_mb_new_blocks().
2. We increment this percpu discard_pa_seq counter when we succeed in allocating blocks and hence while adding the remaining blocks in group's bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
3. We also increment this percpu seq counter when we successfully identify that the bb_prealloc_list is not empty and hence proceed for discarding of those PAs inside ext4_mb_discard_group_preallocations().

Now to make sure that the regular fast path of block allocation is not affected, as a small optimization we only sample the percpu seq counter on that cpu. Only when the block allocation fails and when freed blocks found were 0, that is when we sample percpu seq counter for all cpus using below function ext4_get_discard_pa_seq_sum(). This happens after making sure that all the PAs on grp->bb_prealloc_list got freed.

Quoted 8 lines

TO CHECK: Though in here rcu_barrier only happens in ENOSPC path.
=================================================================
On rcu_barrier() - How expensive it can be?
Does that mean that every thread who is coming and waiting on rcu_barrier() will actually check whether call_rcu() has completed by checking that on every cpu? So will this be a O(n*m) operation? (n = no. of threads, m = no. of cpus).
Or are there some sort of optimization in using rcu_barrier()?

This first part assumes that a single task is executing a series of rcu_barrier() calls sequentially.

Yes, the rcu_barrier() function makes heavy use of batching optimizations and asynchrony. So from an overhead perspective, it queues an RCU callback on each CPU that has at least one callback already queued. It does this queuing in such a way to avoid the need for any additional grace periods. Of course, each callback actually queued will eventually be invoked (function call through a pointer), and the callback will atomically decrement a counter and wake up the rcu_barrier() task if that counter reaches zero. So the CPU overhead is O(M), where M is the number of CPUs, with negligible overhead except for those CPUs that have at least one RCU callback queued.

So the CPU overhead of rcu_barrier() is quite small, at least when taken on a per-CPU basis.

However, the latency can be a bit longer than than that of an RCU grace period. In normal operation, this will normally range from a few milliseconds to a few hundred milliseconds.

And the caller of rcu_barrier() can also do batching. For example, the following:

	make_call_rcu_stop(a);
	rcu_barrier();
	depend_on_callbacks_finished(a);
	make_call_rcu_stop(b);
	rcu_barrier();
	depend_on_callbacks_finished(b);
	...
	make_call_rcu_stop(z);
	rcu_barrier();
	depend_on_callbacks_finished(z);

Can be safely transformed into this:

	make_call_rcu_stop(a);
	make_call_rcu_stop(b);
	...
	make_call_rcu_stop(z);
	rcu_barrier();
	depend_on_callbacks_finished(a);
	depend_on_callbacks_finished(b);
	...
	depend_on_callbacks_finished(z);

That single rcu_barrier() call can cover all 26 instances. Give or take possible software-engineering and maintainability issues, of course.

But what if a large number of tasks are concurrently executing rcu_barrier()?

These will also be batched. The first task will run the rcu_barrier() machinery. Any rcu_barrier() calls that show up before the first one completes will share a single second run of the rcu_barrier() machinery. And any rcu_barrier() calls that show up before this second run of the machinery completes will be handled by a single third run of this machinery. And so on.

Does that help, or am I missing the point of your question?

Thanx, Paul

Quoted 155 lines

---
Note: The other method [1] also used to check for grp->bb_free next time if we couldn't discard anything. But it had below concerns due to which we cannot use that approach.
1. But one suspicion with that was that if grp->bb_free is non-zero for some reason (not sure), then we may result in that loop indefinitely but still won't be able to satisfy any request.
2. To check for grp->bb_free all threads were to wait on grp's spin_lock which might result in increased cpu usage.
3. In case of group's PA allocation (i.e. when file's size is < 1MB for 64K blocksize), there is still a case where ENOSPC could be returned. This happens when the grp->bb_free is set to 0 but those blocks are actually not yet added to PA. Yes, this could actually happen since reducing grp->bb_free and adding those extra blocks in bb_prealloc_list are not done atomically. Hence the race.

[1]: https://patchwork.ozlabs.org/project/linux-ext4/patch/533ac1f5b19c520b08f8c99aec5baf8729185714.1586954511.git.riteshh@linux.ibm.com/

---
 fs/ext4/mballoc.c | 65 +++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 60 insertions(+), 5 deletions(-)
fs/ext4/mballoc.c +60 -5
index a742e51e33b8..6bb08bb3c0ce 100644
@@ -357,6 +357,35 @@ static void ext4_mb_generate_from_pa(struct super_block *sb, void *bitmap,
 static void ext4_mb_generate_from_freelist(struct super_block *sb, void *bitmap,
 						ext4_group_t group);
 
+/*
+ * The algorithm using this percpu seq counter goes below:
+ * 1. We sample the percpu discard_pa_seq counter before trying for block
+ *    allocation in ext4_mb_new_blocks().
+ * 2. We increment this percpu discard_pa_seq counter when we succeed in
+ *    allocating blocks and hence while adding the remaining blocks in group's
+ *    bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
+ * 3. We also increment this percpu seq counter when we successfully identify
+ *    that the bb_prealloc_list is not empty and hence proceed for discarding
+ *    of those PAs inside ext4_mb_discard_group_preallocations().
+ *
+ * Now to make sure that the regular fast path of block allocation is not
+ * affected, as a small optimization we only sample the percpu seq counter
+ * on that cpu. Only when the block allocation fails and when freed blocks
+ * found were 0, that is when we sample percpu seq counter for all cpus using
+ * below function ext4_get_discard_pa_seq_sum(). This happens after making
+ * sure that all the PAs on grp->bb_prealloc_list got freed.
+ */
+DEFINE_PER_CPU(u64, discard_pa_seq);
+static inline u64 ext4_get_discard_pa_seq_sum(void)
+{
+	int __cpu;
+	u64 __seq = 0;
+
+	for_each_possible_cpu(__cpu)
+		__seq += per_cpu(discard_pa_seq, __cpu);
+	return __seq;
+}
+
 static inline void *mb_correct_addr_and_bit(int *bit, void *addr)
 {
 #if BITS_PER_LONG == 64
@@ -3730,6 +3759,7 @@ ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
 	pa->pa_inode = ac->ac_inode;
 
 	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
 	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
 	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
 
@@ -3791,6 +3821,7 @@ ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
 	pa->pa_inode = NULL;
 
 	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
 	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
 	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
 
@@ -3943,6 +3974,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
 	INIT_LIST_HEAD(&list);
 repeat:
 	ext4_lock_group(sb, group);
+	this_cpu_inc(discard_pa_seq);
 	list_for_each_entry_safe(pa, tmp,
 				&grp->bb_prealloc_list, pa_group_list) {
 		spin_lock(&pa->pa_lock);
@@ -4487,14 +4519,35 @@ static int ext4_mb_discard_preallocations(struct super_block *sb, int needed)
 }
 
 static bool ext4_mb_discard_preallocations_should_retry(struct super_block *sb,
-			struct ext4_allocation_context *ac)
+			struct ext4_allocation_context *ac, u64 *seq)
 {
 	int freed;
+	u64 seq_retry = 0;
+	bool ret = false;
 
 	freed = ext4_mb_discard_preallocations(sb, ac->ac_o_ex.fe_len);
-	if (freed)
-		return true;
-	return false;
+	if (freed) {
+		ret = true;
+		goto out_dbg;
+	}
+	/*
+	 * Unless it is ensured that PAs are actually freed, we may hit
+	 * a ENOSPC error since the next time seq may match while the PA blocks
+	 * are still getting freed in ext4_mb_release_inode/group_pa().
+	 * So, rcu_barrier() here is to make sure that any call_rcu queued in
+	 * ext4_mb_discard_group_preallocations() is completed before we
+	 * proceed further to retry for block allocation.
+	 */
+	rcu_barrier();
+	seq_retry = ext4_get_discard_pa_seq_sum();
+	if (seq_retry != *seq) {
+		*seq = seq_retry;
+		ret = true;
+	}
+
+out_dbg:
+	mb_debug(1, "freed %d, retry ? %s\n", freed, ret ? "yes" : "no");
+	return ret;
 }
 
 /*
@@ -4511,6 +4564,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 	ext4_fsblk_t block = 0;
 	unsigned int inquota = 0;
 	unsigned int reserv_clstrs = 0;
+	u64 seq;
 
 	might_sleep();
 	sb = ar->inode->i_sb;
@@ -4572,6 +4626,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 	}
 
 	ac->ac_op = EXT4_MB_HISTORY_PREALLOC;
+	seq = *this_cpu_ptr(&discard_pa_seq);
 	if (!ext4_mb_use_preallocated(ac)) {
 		ac->ac_op = EXT4_MB_HISTORY_ALLOC;
 		ext4_mb_normalize_request(ac, ar);
@@ -4603,7 +4658,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 			ar->len = ac->ac_b_ex.fe_len;
 		}
 	} else {
-		if (ext4_mb_discard_preallocations_should_retry(sb, ac))
+		if (ext4_mb_discard_preallocations_should_retry(sb, ac, &seq))
 			goto repeat;
 		*errp = -ENOSPC;
 	}
Signature · 1 line
2.21.0
#6 lore raw

Re: [RFC 02/20] ext4: Introduce percpu seq counter for freeing blocks(PA) to avoid ENOSPC err

Hello Paul,

Quoted 95 lines · Paul E. McKenney
Quoted 31 lines · Ritesh Harjani

There could be a race in function ext4_mb_discard_group_preallocations() where the 1st thread may iterate through group's bb_prealloc_list and remove all the PAs and add to function's local list head. Now if the 2nd thread comes in to discard the group preallocations, it will see that the group->bb_prealloc_list is empty and will return 0.

Consider for a case where we have less number of groups (for e.g. just group 0),
this may even return an -ENOSPC error from ext4_mb_new_blocks() (where we call for ext4_mb_discard_group_preallocations()). But that is wrong, since 2nd thread should have waited for 1st thread to release all the PAs and should have retried for allocation. Since 1st thread was anyway going to discard the PAs.

The algorithm using this percpu seq counter goes below:
1. We sample the percpu discard_pa_seq counter before trying for block allocation in ext4_mb_new_blocks().
2. We increment this percpu discard_pa_seq counter when we succeed in allocating blocks and hence while adding the remaining blocks in group's bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
3. We also increment this percpu seq counter when we successfully identify that the bb_prealloc_list is not empty and hence proceed for discarding of those PAs inside ext4_mb_discard_group_preallocations().

Now to make sure that the regular fast path of block allocation is not affected, as a small optimization we only sample the percpu seq counter on that cpu. Only when the block allocation fails and when freed blocks found were 0, that is when we sample percpu seq counter for all cpus using below function ext4_get_discard_pa_seq_sum(). This happens after making sure that all the PAs on grp->bb_prealloc_list got freed.

Quoted 8 lines

TO CHECK: Though in here rcu_barrier only happens in ENOSPC path.
=================================================================
On rcu_barrier() - How expensive it can be?
Does that mean that every thread who is coming and waiting on rcu_barrier() will actually check whether call_rcu() has completed by checking that on every cpu? So will this be a O(n*m) operation? (n = no. of threads, m = no. of cpus).
Or are there some sort of optimization in using rcu_barrier()?

This first part assumes that a single task is executing a series of rcu_barrier() calls sequentially.

Yes, the rcu_barrier() function makes heavy use of batching optimizations and asynchrony. So from an overhead perspective, it queues an RCU callback on each CPU that has at least one callback already queued. It does this queuing in such a way to avoid the need for any additional grace periods. Of course, each callback actually queued will eventually be invoked (function call through a pointer), and the callback will atomically decrement a counter and wake up the rcu_barrier() task if that counter reaches zero. So the CPU overhead is O(M), where M is the number of CPUs, with negligible overhead except for those CPUs that have at least one RCU callback queued.

So the CPU overhead of rcu_barrier() is quite small, at least when taken on a per-CPU basis.

However, the latency can be a bit longer than than that of an RCU grace period. In normal operation, this will normally range from a few milliseconds to a few hundred milliseconds.

And the caller of rcu_barrier() can also do batching. For example, the following:

	make_call_rcu_stop(a);
	rcu_barrier();
	depend_on_callbacks_finished(a);
	make_call_rcu_stop(b);
	rcu_barrier();
	depend_on_callbacks_finished(b);
	...
	make_call_rcu_stop(z);
	rcu_barrier();
	depend_on_callbacks_finished(z);

Can be safely transformed into this:

	make_call_rcu_stop(a);
	make_call_rcu_stop(b);
	...
	make_call_rcu_stop(z);
	rcu_barrier();
	depend_on_callbacks_finished(a);
	depend_on_callbacks_finished(b);
	...
	depend_on_callbacks_finished(z);

That single rcu_barrier() call can cover all 26 instances. Give or take possible software-engineering and maintainability issues, of course.

But what if a large number of tasks are concurrently executing rcu_barrier()?

Quoted 6 lines

These will also be batched. The first task will run the rcu_barrier() machinery. Any rcu_barrier() calls that show up before the first one completes will share a single second run of the rcu_barrier() machinery. And any rcu_barrier() calls that show up before this second run of the machinery completes will be handled by a single third run of this machinery. And so on.

Thanks for explaining that. So I was doing more reading on rcu_barrier() code and I think what it essentially does, is depicted from line 1 - 12 below.

So AFAIU,
when there are multiple threads calling rcu_barrier(), what will happen mostly is that 1st thread which calls this func will get the mutex_lock (line 1) and will make sure that all calls to call_rcu() already queued on different cpus are completed before this rcu_barrier() returns. And how exactly this happens is that rcu_barrier() runs it's own rcu_barrier_func() on all of those cpus which have call_rcu() queued up. rcu_barrier_func() while running on each of those cpus then queues up a call_rcu() (on it's tail of call_rcu queue) with rcu_barrier_callback() function. This call_rcu queued on tail will run, after all of previous call_rcu() functions from those per cpu queues are completed. This rcu_barrier_callback() will mostly run after a grace period (like how in general call_rcu() works).

And meanwhile all other threads will sleep while trying to acquire that mutex lock. So at this time there could be something useful which these cpus should be able to do (while waiting on mutex). Now, when these other threads will be able to acquire the mutex_lock() to proceed inside rcu_barrier(), in case if there are no more call_rcu() queued in (since it should have been taken care by previous thread), (checked in line 5 below) then nothing else will be done by rcu_barrier(), and it will simply exit.

Is the above understanding correct?

Instead if I use a spinlock. Then in the worst case what will happen is that most of the threads will just spin on this lock while not being able to do anything useful. Also in case if it's heavy multi-threaded and if we hit this scenario, then this could also result into high cpu usage and sluggish performance since all threads are waiting on spinlock.

My main reason to understand rcu_barrier() functionality is to weigh my options of which one is a better approach to be deployed here. Hence all of these queries :)

1. mutex_lock(&rcu_state.barrier_mutex);
2. init_completion(&rcu_state.barrier_completion);
3. atomic_set(&rcu_state.barrier_cpu_count, 1);

4. for_each_possible_cpu(cpu) {
5. if_we_have_any_call_rcu_pending_on_this_cpu {
6. smp_call_function_single(cpu, rcu_barrier_func, NULL, 1)
7. }
8. }

9. if (atomic_dec_and_test(&rcu_state.barrier_cpu_count))
10. complete(&rcu_state.barrier_completion);

11. wait_for_completion(&rcu_state.barrier_completion);

12. mutex_unlock(&rcu_state.barrier_mutex);

Thanks
-ritesh

Quoted 155 lines
Quoted 155 lines

---
Note: The other method [1] also used to check for grp->bb_free next time if we couldn't discard anything. But it had below concerns due to which we cannot use that approach.
1. But one suspicion with that was that if grp->bb_free is non-zero for some reason (not sure), then we may result in that loop indefinitely but still won't be able to satisfy any request.
2. To check for grp->bb_free all threads were to wait on grp's spin_lock which might result in increased cpu usage.
3. In case of group's PA allocation (i.e. when file's size is < 1MB for 64K blocksize), there is still a case where ENOSPC could be returned. This happens when the grp->bb_free is set to 0 but those blocks are actually not yet added to PA. Yes, this could actually happen since reducing grp->bb_free and adding those extra blocks in bb_prealloc_list are not done atomically. Hence the race.

[1]: https://patchwork.ozlabs.org/project/linux-ext4/patch/533ac1f5b19c520b08f8c99aec5baf8729185714.1586954511.git.riteshh@linux.ibm.com/

---
  fs/ext4/mballoc.c | 65 +++++++++++++++++++++++++++++++++++++++++++----
  1 file changed, 60 insertions(+), 5 deletions(-)
fs/ext4/mballoc.c +60 -5
index a742e51e33b8..6bb08bb3c0ce 100644
@@ -357,6 +357,35 @@ static void ext4_mb_generate_from_pa(struct super_block *sb, void *bitmap,
  static void ext4_mb_generate_from_freelist(struct super_block *sb, void *bitmap,
  						ext4_group_t group);
 
+/*
+ * The algorithm using this percpu seq counter goes below:
+ * 1. We sample the percpu discard_pa_seq counter before trying for block
+ *    allocation in ext4_mb_new_blocks().
+ * 2. We increment this percpu discard_pa_seq counter when we succeed in
+ *    allocating blocks and hence while adding the remaining blocks in group's
+ *    bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
+ * 3. We also increment this percpu seq counter when we successfully identify
+ *    that the bb_prealloc_list is not empty and hence proceed for discarding
+ *    of those PAs inside ext4_mb_discard_group_preallocations().
+ *
+ * Now to make sure that the regular fast path of block allocation is not
+ * affected, as a small optimization we only sample the percpu seq counter
+ * on that cpu. Only when the block allocation fails and when freed blocks
+ * found were 0, that is when we sample percpu seq counter for all cpus using
+ * below function ext4_get_discard_pa_seq_sum(). This happens after making
+ * sure that all the PAs on grp->bb_prealloc_list got freed.
+ */
+DEFINE_PER_CPU(u64, discard_pa_seq);
+static inline u64 ext4_get_discard_pa_seq_sum(void)
+{
+	int __cpu;
+	u64 __seq = 0;
+
+	for_each_possible_cpu(__cpu)
+		__seq += per_cpu(discard_pa_seq, __cpu);
+	return __seq;
+}
+
  static inline void *mb_correct_addr_and_bit(int *bit, void *addr)
  {
  #if BITS_PER_LONG == 64
@@ -3730,6 +3759,7 @@ ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
  	pa->pa_inode = ac->ac_inode;
 
  	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
  	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
  	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
 
@@ -3791,6 +3821,7 @@ ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
  	pa->pa_inode = NULL;
 
  	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
  	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
  	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
 
@@ -3943,6 +3974,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
  	INIT_LIST_HEAD(&list);
  repeat:
  	ext4_lock_group(sb, group);
+	this_cpu_inc(discard_pa_seq);
  	list_for_each_entry_safe(pa, tmp,
  				&grp->bb_prealloc_list, pa_group_list) {
  		spin_lock(&pa->pa_lock);
@@ -4487,14 +4519,35 @@ static int ext4_mb_discard_preallocations(struct super_block *sb, int needed)
  }
 
  static bool ext4_mb_discard_preallocations_should_retry(struct super_block *sb,
-			struct ext4_allocation_context *ac)
+			struct ext4_allocation_context *ac, u64 *seq)
  {
  	int freed;
+	u64 seq_retry = 0;
+	bool ret = false;
 
  	freed = ext4_mb_discard_preallocations(sb, ac->ac_o_ex.fe_len);
-	if (freed)
-		return true;
-	return false;
+	if (freed) {
+		ret = true;
+		goto out_dbg;
+	}
+	/*
+	 * Unless it is ensured that PAs are actually freed, we may hit
+	 * a ENOSPC error since the next time seq may match while the PA blocks
+	 * are still getting freed in ext4_mb_release_inode/group_pa().
+	 * So, rcu_barrier() here is to make sure that any call_rcu queued in
+	 * ext4_mb_discard_group_preallocations() is completed before we
+	 * proceed further to retry for block allocation.
+	 */
+	rcu_barrier();
+	seq_retry = ext4_get_discard_pa_seq_sum();
+	if (seq_retry != *seq) {
+		*seq = seq_retry;
+		ret = true;
+	}
+
+out_dbg:
+	mb_debug(1, "freed %d, retry ? %s\n", freed, ret ? "yes" : "no");
+	return ret;
  }
 
  /*
@@ -4511,6 +4564,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
  	ext4_fsblk_t block = 0;
  	unsigned int inquota = 0;
  	unsigned int reserv_clstrs = 0;
+	u64 seq;
 
  	might_sleep();
  	sb = ar->inode->i_sb;
@@ -4572,6 +4626,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
  	}
 
  	ac->ac_op = EXT4_MB_HISTORY_PREALLOC;
+	seq = *this_cpu_ptr(&discard_pa_seq);
  	if (!ext4_mb_use_preallocated(ac)) {
  		ac->ac_op = EXT4_MB_HISTORY_ALLOC;
  		ext4_mb_normalize_request(ac, ar);
@@ -4603,7 +4658,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
  			ar->len = ac->ac_b_ex.fe_len;
  		}
  	} else {
-		if (ext4_mb_discard_preallocations_should_retry(sb, ac))
+		if (ext4_mb_discard_preallocations_should_retry(sb, ac, &seq))
  			goto repeat;
  		*errp = -ENOSPC;
  	}
Signature · 1 line
2.21.0
#7 lore raw

Re: [RFC 02/20] ext4: Introduce percpu seq counter for freeing blocks(PA) to avoid ENOSPC err

Quoted 123 lines · Ritesh Harjani

Hello Paul,

Quoted 95 lines · Paul E. McKenney
Quoted 31 lines · Ritesh Harjani

There could be a race in function ext4_mb_discard_group_preallocations() where the 1st thread may iterate through group's bb_prealloc_list and remove all the PAs and add to function's local list head. Now if the 2nd thread comes in to discard the group preallocations, it will see that the group->bb_prealloc_list is empty and will return 0.

Consider for a case where we have less number of groups (for e.g. just group 0),
this may even return an -ENOSPC error from ext4_mb_new_blocks() (where we call for ext4_mb_discard_group_preallocations()). But that is wrong, since 2nd thread should have waited for 1st thread to release all the PAs and should have retried for allocation. Since 1st thread was anyway going to discard the PAs.

The algorithm using this percpu seq counter goes below:
1. We sample the percpu discard_pa_seq counter before trying for block allocation in ext4_mb_new_blocks().
2. We increment this percpu discard_pa_seq counter when we succeed in allocating blocks and hence while adding the remaining blocks in group's bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
3. We also increment this percpu seq counter when we successfully identify that the bb_prealloc_list is not empty and hence proceed for discarding of those PAs inside ext4_mb_discard_group_preallocations().

Now to make sure that the regular fast path of block allocation is not affected, as a small optimization we only sample the percpu seq counter on that cpu. Only when the block allocation fails and when freed blocks found were 0, that is when we sample percpu seq counter for all cpus using below function ext4_get_discard_pa_seq_sum(). This happens after making sure that all the PAs on grp->bb_prealloc_list got freed.

Quoted 8 lines

TO CHECK: Though in here rcu_barrier only happens in ENOSPC path.
=================================================================
On rcu_barrier() - How expensive it can be?
Does that mean that every thread who is coming and waiting on rcu_barrier() will actually check whether call_rcu() has completed by checking that on every cpu? So will this be a O(n*m) operation? (n = no. of threads, m = no. of cpus).
Or are there some sort of optimization in using rcu_barrier()?

This first part assumes that a single task is executing a series of rcu_barrier() calls sequentially.

Yes, the rcu_barrier() function makes heavy use of batching optimizations and asynchrony. So from an overhead perspective, it queues an RCU callback on each CPU that has at least one callback already queued. It does this queuing in such a way to avoid the need for any additional grace periods. Of course, each callback actually queued will eventually be invoked (function call through a pointer), and the callback will atomically decrement a counter and wake up the rcu_barrier() task if that counter reaches zero. So the CPU overhead is O(M), where M is the number of CPUs, with negligible overhead except for those CPUs that have at least one RCU callback queued.

So the CPU overhead of rcu_barrier() is quite small, at least when taken on a per-CPU basis.

However, the latency can be a bit longer than than that of an RCU grace period. In normal operation, this will normally range from a few milliseconds to a few hundred milliseconds.

And the caller of rcu_barrier() can also do batching. For example, the following:

	make_call_rcu_stop(a);
	rcu_barrier();
	depend_on_callbacks_finished(a);
	make_call_rcu_stop(b);
	rcu_barrier();
	depend_on_callbacks_finished(b);
	...
	make_call_rcu_stop(z);
	rcu_barrier();
	depend_on_callbacks_finished(z);

Can be safely transformed into this:

	make_call_rcu_stop(a);
	make_call_rcu_stop(b);
	...
	make_call_rcu_stop(z);
	rcu_barrier();
	depend_on_callbacks_finished(a);
	depend_on_callbacks_finished(b);
	...
	depend_on_callbacks_finished(z);

That single rcu_barrier() call can cover all 26 instances. Give or take possible software-engineering and maintainability issues, of course.

But what if a large number of tasks are concurrently executing rcu_barrier()?

Quoted 6 lines

These will also be batched. The first task will run the rcu_barrier() machinery. Any rcu_barrier() calls that show up before the first one completes will share a single second run of the rcu_barrier() machinery. And any rcu_barrier() calls that show up before this second run of the machinery completes will be handled by a single third run of this machinery. And so on.

Thanks for explaining that. So I was doing more reading on rcu_barrier() code and I think what it essentially does, is depicted from line 1 - 12 below.

So AFAIU,
when there are multiple threads calling rcu_barrier(), what will happen mostly is that 1st thread which calls this func will get the mutex_lock (line 1) and will make sure that all calls to call_rcu() already queued on different cpus are completed before this rcu_barrier() returns. And how exactly this happens is that rcu_barrier() runs it's own rcu_barrier_func() on all of those cpus which have call_rcu() queued up. rcu_barrier_func() while running on each of those cpus then queues up a call_rcu() (on it's tail of call_rcu queue) with rcu_barrier_callback() function. This call_rcu queued on tail will run, after all of previous call_rcu() functions from those per cpu queues are completed. This rcu_barrier_callback() will mostly run after a grace period (like how in general call_rcu() works).

Quoted 8 lines

And meanwhile all other threads will sleep while trying to acquire that mutex lock. So at this time there could be something useful which these cpus should be able to do (while waiting on mutex). Now, when these other threads will be able to acquire the mutex_lock() to proceed inside rcu_barrier(), in case if there are no more call_rcu() queued in (since it should have been taken care by previous thread), (checked in line 5 below) then nothing else will be done by rcu_barrier(), and it will simply exit.

True enough. However, on a busy system it is very likely that there will be additional callbacks enqueued somewhere by that time, and so the next task to acquire the mutex will have to wait. However, the third task might well be able to skip the whole process based on the ->barrier_sequence counter in the rcu_state structure. (The ->barrier_sequence manipulations are not shown in your steps below, but they lead to the "if (rcu_seq_done" early exit just after the mutex is acquired.

Quoted 1 line

Is the above understanding correct?

Reasonably close.

Quoted 6 lines

Instead if I use a spinlock. Then in the worst case what will happen is that most of the threads will just spin on this lock while not being able to do anything useful. Also in case if it's heavy multi-threaded and if we hit this scenario, then this could also result into high cpu usage and sluggish performance since all threads are waiting on spinlock.

It can also result in deadlock because something holding a pure spinlock will need to do a wait_for_completion() while holding that spinlock. And of course, lockdep will complain bitterly, as well it should. ;-)

You also don't get to drop the lock before doing the wait_for_completion() without adding some sort of synchronization preventing the next task from using the rcu_data structure's ->barrier_head before the current task is done with them.

Quoted 3 lines

My main reason to understand rcu_barrier() functionality is to weigh my options of which one is a better approach to be deployed here. Hence all of these queries :)

Why not just try it and then measure the actual effect on performance?

Also, given actual measurements, there might be things I could do to speed this up, for example, applying funnel locking to decrease memory contention on ->barrier_mutex, should that turn out to be a real problem.

But it all depends on whether there is an actual performance problem, and if so, what aspect of the overall algorithm is in trouble.

Thanx, Paul

Quoted 168 lines

1. mutex_lock(&rcu_state.barrier_mutex);
2. init_completion(&rcu_state.barrier_completion);
3. atomic_set(&rcu_state.barrier_cpu_count, 1);

4. for_each_possible_cpu(cpu) {
5. if_we_have_any_call_rcu_pending_on_this_cpu {
6. smp_call_function_single(cpu, rcu_barrier_func, NULL, 1)
7. }
8. }

9. if (atomic_dec_and_test(&rcu_state.barrier_cpu_count))
10. complete(&rcu_state.barrier_completion);

11. wait_for_completion(&rcu_state.barrier_completion);

12. mutex_unlock(&rcu_state.barrier_mutex);

Thanks
-ritesh

Quoted 145 lines
Quoted 145 lines

---
Note: The other method [1] also used to check for grp->bb_free next time if we couldn't discard anything. But it had below concerns due to which we cannot use that approach.
1. But one suspicion with that was that if grp->bb_free is non-zero for some reason (not sure), then we may result in that loop indefinitely but still won't be able to satisfy any request.
2. To check for grp->bb_free all threads were to wait on grp's spin_lock which might result in increased cpu usage.
3. In case of group's PA allocation (i.e. when file's size is < 1MB for 64K blocksize), there is still a case where ENOSPC could be returned. This happens when the grp->bb_free is set to 0 but those blocks are actually not yet added to PA. Yes, this could actually happen since reducing grp->bb_free and adding those extra blocks in bb_prealloc_list are not done atomically. Hence the race.

[1]: https://patchwork.ozlabs.org/project/linux-ext4/patch/533ac1f5b19c520b08f8c99aec5baf8729185714.1586954511.git.riteshh@linux.ibm.com/

---
  fs/ext4/mballoc.c | 65 +++++++++++++++++++++++++++++++++++++++++++----
  1 file changed, 60 insertions(+), 5 deletions(-)
fs/ext4/mballoc.c +60 -5
index a742e51e33b8..6bb08bb3c0ce 100644
@@ -357,6 +357,35 @@ static void ext4_mb_generate_from_pa(struct super_block *sb, void *bitmap,
  static void ext4_mb_generate_from_freelist(struct super_block *sb, void *bitmap,
  						ext4_group_t group);
+/*
+ * The algorithm using this percpu seq counter goes below:
+ * 1. We sample the percpu discard_pa_seq counter before trying for block
+ *    allocation in ext4_mb_new_blocks().
+ * 2. We increment this percpu discard_pa_seq counter when we succeed in
+ *    allocating blocks and hence while adding the remaining blocks in group's
+ *    bb_prealloc_list (ext4_mb_new_inode_pa/ext4_mb_new_group_pa).
+ * 3. We also increment this percpu seq counter when we successfully identify
+ *    that the bb_prealloc_list is not empty and hence proceed for discarding
+ *    of those PAs inside ext4_mb_discard_group_preallocations().
+ *
+ * Now to make sure that the regular fast path of block allocation is not
+ * affected, as a small optimization we only sample the percpu seq counter
+ * on that cpu. Only when the block allocation fails and when freed blocks
+ * found were 0, that is when we sample percpu seq counter for all cpus using
+ * below function ext4_get_discard_pa_seq_sum(). This happens after making
+ * sure that all the PAs on grp->bb_prealloc_list got freed.
+ */
+DEFINE_PER_CPU(u64, discard_pa_seq);
+static inline u64 ext4_get_discard_pa_seq_sum(void)
+{
+	int __cpu;
+	u64 __seq = 0;
+
+	for_each_possible_cpu(__cpu)
+		__seq += per_cpu(discard_pa_seq, __cpu);
+	return __seq;
+}
+
  static inline void *mb_correct_addr_and_bit(int *bit, void *addr)
  {
  #if BITS_PER_LONG == 64
@@ -3730,6 +3759,7 @@ ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
  	pa->pa_inode = ac->ac_inode;
  	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
  	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
  	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
@@ -3791,6 +3821,7 @@ ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
  	pa->pa_inode = NULL;
  	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
+	this_cpu_inc(discard_pa_seq);
  	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
  	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
@@ -3943,6 +3974,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
  	INIT_LIST_HEAD(&list);
  repeat:
  	ext4_lock_group(sb, group);
+	this_cpu_inc(discard_pa_seq);
  	list_for_each_entry_safe(pa, tmp,
  				&grp->bb_prealloc_list, pa_group_list) {
  		spin_lock(&pa->pa_lock);
@@ -4487,14 +4519,35 @@ static int ext4_mb_discard_preallocations(struct super_block *sb, int needed)
  }
  static bool ext4_mb_discard_preallocations_should_retry(struct super_block *sb,
-			struct ext4_allocation_context *ac)
+			struct ext4_allocation_context *ac, u64 *seq)
  {
  	int freed;
+	u64 seq_retry = 0;
+	bool ret = false;
  	freed = ext4_mb_discard_preallocations(sb, ac->ac_o_ex.fe_len);
-	if (freed)
-		return true;
-	return false;
+	if (freed) {
+		ret = true;
+		goto out_dbg;
+	}
+	/*
+	 * Unless it is ensured that PAs are actually freed, we may hit
+	 * a ENOSPC error since the next time seq may match while the PA blocks
+	 * are still getting freed in ext4_mb_release_inode/group_pa().
+	 * So, rcu_barrier() here is to make sure that any call_rcu queued in
+	 * ext4_mb_discard_group_preallocations() is completed before we
+	 * proceed further to retry for block allocation.
+	 */
+	rcu_barrier();
+	seq_retry = ext4_get_discard_pa_seq_sum();
+	if (seq_retry != *seq) {
+		*seq = seq_retry;
+		ret = true;
+	}
+
+out_dbg:
+	mb_debug(1, "freed %d, retry ? %s\n", freed, ret ? "yes" : "no");
+	return ret;
  }
  /*
@@ -4511,6 +4564,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
  	ext4_fsblk_t block = 0;
  	unsigned int inquota = 0;
  	unsigned int reserv_clstrs = 0;
+	u64 seq;
  	might_sleep();
  	sb = ar->inode->i_sb;
@@ -4572,6 +4626,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
  	}
  	ac->ac_op = EXT4_MB_HISTORY_PREALLOC;
+	seq = *this_cpu_ptr(&discard_pa_seq);
  	if (!ext4_mb_use_preallocated(ac)) {
  		ac->ac_op = EXT4_MB_HISTORY_ALLOC;
  		ext4_mb_normalize_request(ac, ar);
@@ -4603,7 +4658,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
  			ar->len = ac->ac_b_ex.fe_len;
  		}
  	} else {
-		if (ext4_mb_discard_preallocations_should_retry(sb, ac))
+		if (ext4_mb_discard_preallocations_should_retry(sb, ac, &seq))
  			goto repeat;
  		*errp = -ENOSPC;
  	}
Signature · 1 line
2.21.0
#8 lore raw

RFC 3/20 ext4: mballoc: Do print bb_free info even when it is 0

In reply to Ritesh Harjani

Improve the debugging msg by also printing even if bb_free is 0.

---
 fs/ext4/mballoc.c | 2 --
 1 file changed, 2 deletions(-)
fs/ext4/mballoc.c +0 -2
index 6bb08bb3c0ce..478b33fb1183 100644
@@ -4202,8 +4202,6 @@ static void ext4_mb_show_ac(struct ext4_allocation_context *ac)
 		}
 		ext4_unlock_group(sb, i);
 
-		if (grp->bb_free == 0)
-			continue;
 		printk(KERN_ERR "%u: %d/%d \n",
 		       i, grp->bb_free, grp->bb_fragments);
 	}
Signature · 1 line
2.21.0
#9 lore raw

RFC 4/20 ext4: mballoc: Refactor ext4_mb_show_ac()

In reply to Ritesh Harjani

This factors out ext4_mb_show_pa() function to show all the group's preallocation info. This could be useful info to be added in later patches.

There should be no functionality change in this patch.

---
 fs/ext4/mballoc.c | 65 +++++++++++++++++++++++++++++------------------
 1 file changed, 40 insertions(+), 25 deletions(-)
fs/ext4/mballoc.c +40 -25
index 478b33fb1183..d7ae5a092796 100644
@@ -4152,38 +4152,16 @@ void ext4_discard_preallocations(struct inode *inode)
 }
 
 #ifdef CONFIG_EXT4_DEBUG
-static void ext4_mb_show_ac(struct ext4_allocation_context *ac)
+static inline void ext4_mb_show_pa(struct super_block *sb)
 {
-	struct super_block *sb = ac->ac_sb;
-	ext4_group_t ngroups, i;
+	ext4_group_t i, ngroups;
 
 	if (!ext4_mballoc_debug ||
 	    (EXT4_SB(sb)->s_mount_flags & EXT4_MF_FS_ABORTED))
 		return;
 
-	ext4_msg(ac->ac_sb, KERN_ERR, "Can't allocate:"
-			" Allocation context details:");
-	ext4_msg(ac->ac_sb, KERN_ERR, "status %d flags %d",
-			ac->ac_status, ac->ac_flags);
-	ext4_msg(ac->ac_sb, KERN_ERR, "orig %lu/%lu/%lu@%lu, "
-		 	"goal %lu/%lu/%lu@%lu, "
-			"best %lu/%lu/%lu@%lu cr %d",
-			(unsigned long)ac->ac_o_ex.fe_group,
-			(unsigned long)ac->ac_o_ex.fe_start,
-			(unsigned long)ac->ac_o_ex.fe_len,
-			(unsigned long)ac->ac_o_ex.fe_logical,
-			(unsigned long)ac->ac_g_ex.fe_group,
-			(unsigned long)ac->ac_g_ex.fe_start,
-			(unsigned long)ac->ac_g_ex.fe_len,
-			(unsigned long)ac->ac_g_ex.fe_logical,
-			(unsigned long)ac->ac_b_ex.fe_group,
-			(unsigned long)ac->ac_b_ex.fe_start,
-			(unsigned long)ac->ac_b_ex.fe_len,
-			(unsigned long)ac->ac_b_ex.fe_logical,
-			(int)ac->ac_criteria);
-	ext4_msg(ac->ac_sb, KERN_ERR, "%d found", ac->ac_found);
-	ext4_msg(ac->ac_sb, KERN_ERR, "groups: ");
 	ngroups = ext4_get_groups_count(sb);
+	ext4_msg(sb, KERN_ERR, "groups: ");
 	for (i = 0; i < ngroups; i++) {
 		struct ext4_group_info *grp = ext4_get_group_info(sb, i);
 		struct ext4_prealloc_space *pa;
@@ -4207,9 +4185,46 @@ static void ext4_mb_show_ac(struct ext4_allocation_context *ac)
 	}
 	printk(KERN_ERR "\n");
 }
+
+static void ext4_mb_show_ac(struct ext4_allocation_context *ac)
+{
+	struct super_block *sb = ac->ac_sb;
+
+	if (!ext4_mballoc_debug ||
+	    (EXT4_SB(sb)->s_mount_flags & EXT4_MF_FS_ABORTED))
+		return;
+
+	ext4_msg(sb, KERN_ERR, "Can't allocate:"
+			" Allocation context details:");
+	ext4_msg(sb, KERN_ERR, "status %d flags %d",
+			ac->ac_status, ac->ac_flags);
+	ext4_msg(sb, KERN_ERR, "orig %lu/%lu/%lu@%lu, "
+			"goal %lu/%lu/%lu@%lu, "
+			"best %lu/%lu/%lu@%lu cr %d",
+			(unsigned long)ac->ac_o_ex.fe_group,
+			(unsigned long)ac->ac_o_ex.fe_start,
+			(unsigned long)ac->ac_o_ex.fe_len,
+			(unsigned long)ac->ac_o_ex.fe_logical,
+			(unsigned long)ac->ac_g_ex.fe_group,
+			(unsigned long)ac->ac_g_ex.fe_start,
+			(unsigned long)ac->ac_g_ex.fe_len,
+			(unsigned long)ac->ac_g_ex.fe_logical,
+			(unsigned long)ac->ac_b_ex.fe_group,
+			(unsigned long)ac->ac_b_ex.fe_start,
+			(unsigned long)ac->ac_b_ex.fe_len,
+			(unsigned long)ac->ac_b_ex.fe_logical,
+			(int)ac->ac_criteria);
+	ext4_msg(sb, KERN_ERR, "%d found", ac->ac_found);
+	ext4_mb_show_pa(sb);
+}
 #else
+static inline void ext4_mb_show_pa(struct super_block *sb)
+{
+	return;
+}
 static inline void ext4_mb_show_ac(struct ext4_allocation_context *ac)
 {
+	ext4_mb_show_pa(ac->ac_sb);
 	return;
 }
 #endif
Signature · 1 line
2.21.0
#10 lore raw

RFC 5/20 ext4: mballoc: Add more mb_debug() msgs

In reply to Ritesh Harjani

This patch adds some more debugging mb_debug() msgs to help improve mballoc code debugging.
Other than adding more mb_debug() msgs at few more places, there should be no other functionality change in this patch.

---
 fs/ext4/mballoc.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)
fs/ext4/mballoc.c +14 -4
index d7ae5a092796..2acabc3927e0 100644
@@ -2137,7 +2137,7 @@ static noinline_for_stack int
 ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
 {
 	ext4_group_t ngroups, group, i;
-	int cr;
+	int cr = -1;
 	int err = 0, first_err = 0;
 	struct ext4_sb_info *sbi;
 	struct super_block *sb;
@@ -2289,6 +2289,10 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
 out:
 	if (!err && ac->ac_status != AC_STATUS_FOUND && first_err)
 		err = first_err;
+
+	mb_debug(1, "Best len %d, origin len %d, ac_status %u, ac_flags 0x%x, cr %d ret %d\n",
+		 ac->ac_b_ex.fe_len, ac->ac_o_ex.fe_len, ac->ac_status,
+		 ac->ac_flags, cr, err);
 	return err;
 }
 
@@ -3949,7 +3953,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
 	mb_debug(1, "discard preallocation for group %u\n", group);
 
 	if (list_empty(&grp->bb_prealloc_list))
-		return 0;
+		goto out_dbg;
 
 	bitmap_bh = ext4_read_block_bitmap(sb, group);
 	if (IS_ERR(bitmap_bh)) {
@@ -3957,7 +3961,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
 		ext4_error_err(sb, -err,
 			       "Error %d reading block bitmap for %u",
 			       err, group);
-		return 0;
+		goto out_dbg;
 	}
 
 	err = ext4_mb_load_buddy(sb, group, &e4b);
@@ -3965,7 +3969,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
 		ext4_warning(sb, "Error %d loading buddy information for %u",
 			     err, group);
 		put_bh(bitmap_bh);
-		return 0;
+		goto out_dbg;
 	}
 
 	if (needed == 0)
@@ -4011,6 +4015,8 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
 	/* found anything to free? */
 	if (list_empty(&list)) {
 		BUG_ON(free != 0);
+		mb_debug(1, "Someone else may have freed PA for this group %u\n",
+			 group);
 		goto out;
 	}
 
@@ -4035,6 +4041,9 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
 	ext4_unlock_group(sb, group);
 	ext4_mb_unload_buddy(&e4b);
 	put_bh(bitmap_bh);
+out_dbg:
+	mb_debug(1, "discarded (%d) blocks preallocated for group %u bb_free (%d)\n",
+		 free, group, grp->bb_free);
 	return free;
 }
 
@@ -4602,6 +4611,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
 			ar->len = ar->len >> 1;
 		}
 		if (!ar->len) {
+			ext4_mb_show_pa(sb);
 			*errp = -ENOSPC;
 			return 0;
 		}
Signature · 1 line
2.21.0
#11 lore raw

RFC 6/20 ext4: mballoc: Correct the mb_debug() format specifier for pa_len var

In reply to Ritesh Harjani

pa->pa_len is an integer. Fix all of the format specifier used in mb_debug() for pa_len to %d instead of %u.

As such no functionality change in this patch.

---
 fs/ext4/mballoc.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
fs/ext4/mballoc.c +5 -5
index 2acabc3927e0..a4d9abf97193 100644
@@ -3749,7 +3749,7 @@ ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
 	pa->pa_deleted = 0;
 	pa->pa_type = MB_INODE_PA;
 
-	mb_debug(1, "new inode pa %p: %llu/%u for %u\n", pa,
+	mb_debug(1, "new inode pa %p: %llu/%d for %u\n", pa,
 			pa->pa_pstart, pa->pa_len, pa->pa_lstart);
 	trace_ext4_mb_new_inode_pa(ac, pa);
 
@@ -3810,7 +3810,7 @@ ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
 	pa->pa_deleted = 0;
 	pa->pa_type = MB_GROUP_PA;
 
-	mb_debug(1, "new group pa %p: %llu/%u for %u\n", pa,
+	mb_debug(1, "new group pa %p: %llu/%d for %u\n", pa,
 			pa->pa_pstart, pa->pa_len, pa->pa_lstart);
 	trace_ext4_mb_new_group_pa(ac, pa);
 
@@ -3893,10 +3893,10 @@ ext4_mb_release_inode_pa(struct ext4_buddy *e4b, struct buffer_head *bitmap_bh,
 	}
 	if (free != pa->pa_free) {
 		ext4_msg(e4b->bd_sb, KERN_CRIT,
-			 "pa %p: logic %lu, phys. %lu, len %lu",
+			 "pa %p: logic %lu, phys. %lu, len %d",
 			 pa, (unsigned long) pa->pa_lstart,
 			 (unsigned long) pa->pa_pstart,
-			 (unsigned long) pa->pa_len);
+			 pa->pa_len);
 		ext4_grp_locked_error(sb, group, 0, 0, "free %u, pa_free %u",
 					free, pa->pa_free);
 		/*
@@ -4184,7 +4184,7 @@ static inline void ext4_mb_show_pa(struct super_block *sb)
 			ext4_get_group_no_and_offset(sb, pa->pa_pstart,
 						     NULL, &start);
 			spin_unlock(&pa->pa_lock);
-			printk(KERN_ERR "PA:%u:%d:%u \n", i,
+			printk(KERN_ERR "PA:%u:%d:%d \n", i,
 			       start, pa->pa_len);
 		}
 		ext4_unlock_group(sb, i);
Signature · 1 line
2.21.0
#12 lore raw

RFC 7/20 ext4: mballoc: Fix few other format specifier in mb_debug()

In reply to Ritesh Harjani

Fix few other format specifiers in mb_debug() msgs. As such no other functionality change in this patch.

---
 fs/ext4/mballoc.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
fs/ext4/mballoc.c +6 -6
index a4d9abf97193..2999b41bb5f8 100644
@@ -3309,8 +3309,8 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
 		ac->ac_flags |= EXT4_MB_HINT_TRY_GOAL;
 	}
 
-	mb_debug(1, "goal: %u(was %u) blocks at %u\n", (unsigned) size,
-		(unsigned) orig_size, (unsigned) start);
+	mb_debug(1, "goal: %lld(was %lld) blocks at %u\n", size, orig_size,
+		 start);
 }
 
 static void ext4_mb_collect_stats(struct ext4_allocation_context *ac)
@@ -3399,7 +3399,7 @@ static void ext4_mb_use_inode_pa(struct ext4_allocation_context *ac,
 	BUG_ON(pa->pa_free < len);
 	pa->pa_free -= len;
 
-	mb_debug(1, "use %llu/%u from inode pa %p\n", start, len, pa);
+	mb_debug(1, "use %llu/%d from inode pa %p\n", start, len, pa);
 }
 
 /*
@@ -3606,7 +3606,7 @@ void ext4_mb_generate_from_pa(struct super_block *sb, void *bitmap,
 		ext4_set_bits(bitmap, start, len);
 		preallocated += len;
 	}
-	mb_debug(1, "preallocated %u for group %u\n", preallocated, group);
+	mb_debug(1, "preallocated %d for group %u\n", preallocated, group);
 }
 
 static void ext4_mb_pa_callback(struct rcu_head *head)
@@ -4205,7 +4205,7 @@ static void ext4_mb_show_ac(struct ext4_allocation_context *ac)
 
 	ext4_msg(sb, KERN_ERR, "Can't allocate:"
 			" Allocation context details:");
-	ext4_msg(sb, KERN_ERR, "status %d flags %d",
+	ext4_msg(sb, KERN_ERR, "status %u flags 0x%x",
 			ac->ac_status, ac->ac_flags);
 	ext4_msg(sb, KERN_ERR, "orig %lu/%lu/%lu@%lu, "
 			"goal %lu/%lu/%lu@%lu, "
@@ -4223,7 +4223,7 @@ static void ext4_mb_show_ac(struct ext4_allocation_context *ac)
 			(unsigned long)ac->ac_b_ex.fe_len,
 			(unsigned long)ac->ac_b_ex.fe_logical,
 			(int)ac->ac_criteria);
-	ext4_msg(sb, KERN_ERR, "%d found", ac->ac_found);
+	ext4_msg(sb, KERN_ERR, "%u found", ac->ac_found);
 	ext4_mb_show_pa(sb);
 }
 #else
Signature · 1 line
2.21.0
#13 lore raw

RFC 8/20 ext4: mballoc: Simplify error handling in ext4_init_mballoc()

In reply to Ritesh Harjani

This patch simplifies error handling logic in ext4_init_mballoc(), by adding all the cleanups at one place at the end of that function.

There should be no functionality change in this patch.

---
 fs/ext4/mballoc.c | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)
fs/ext4/mballoc.c +13 -10
index 2999b41bb5f8..10f295c61ccb 100644
@@ -2942,23 +2942,26 @@ int __init ext4_init_mballoc(void)
 	ext4_pspace_cachep = KMEM_CACHE(ext4_prealloc_space,
 					SLAB_RECLAIM_ACCOUNT);
 	if (ext4_pspace_cachep == NULL)
-		return -ENOMEM;
+		goto out;
 
 	ext4_ac_cachep = KMEM_CACHE(ext4_allocation_context,
 				    SLAB_RECLAIM_ACCOUNT);
-	if (ext4_ac_cachep == NULL) {
-		kmem_cache_destroy(ext4_pspace_cachep);
-		return -ENOMEM;
-	}
+	if (ext4_ac_cachep == NULL)
+		goto out_pa_free;
 
 	ext4_free_data_cachep = KMEM_CACHE(ext4_free_data,
 					   SLAB_RECLAIM_ACCOUNT);
-	if (ext4_free_data_cachep == NULL) {
-		kmem_cache_destroy(ext4_pspace_cachep);
-		kmem_cache_destroy(ext4_ac_cachep);
-		return -ENOMEM;
-	}
+	if (ext4_free_data_cachep == NULL)
+		goto out_ac_free;
+
 	return 0;
+
+out_ac_free:
+	kmem_cache_destroy(ext4_ac_cachep);
+out_pa_free:
+	kmem_cache_destroy(ext4_pspace_cachep);
+out:
+	return -ENOMEM;
 }
 
 void ext4_exit_mballoc(void)
Signature · 1 line
2.21.0
#14 lore raw

RFC 9/20 ext4: mballoc: Make ext4_mb_use_preallocated() return type as bool

In reply to Ritesh Harjani

Change return type of function ext4_mb_use_preallocated() to bool to better reflect what this function can return.

There should be no functionality change in this patch.

---
 fs/ext4/mballoc.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)
fs/ext4/mballoc.c +7 -7
index 10f295c61ccb..6e7232fd109e 100644
@@ -3461,7 +3461,7 @@ ext4_mb_check_group_pa(ext4_fsblk_t goal_block,
 /*
  * search goal blocks in preallocated space
  */
-static noinline_for_stack int
+static noinline_for_stack bool
 ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
 {
 	struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
@@ -3473,7 +3473,7 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
 
 	/* only data can be preallocated */
 	if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
-		return 0;
+		return false;
 
 	/* first, try per-file preallocation */
 	rcu_read_lock();
@@ -3500,7 +3500,7 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
 			spin_unlock(&pa->pa_lock);
 			ac->ac_criteria = 10;
 			rcu_read_unlock();
-			return 1;
+			return true;
 		}
 		spin_unlock(&pa->pa_lock);
 	}
@@ -3508,12 +3508,12 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
 
 	/* can we use group allocation? */
 	if (!(ac->ac_flags & EXT4_MB_HINT_GROUP_ALLOC))
-		return 0;
+		return false;
 
 	/* inode may have no locality group for some reason */
 	lg = ac->ac_lg;
 	if (lg == NULL)
-		return 0;
+		return false;
 	order  = fls(ac->ac_o_ex.fe_len) - 1;
 	if (order > PREALLOC_TB_SIZE - 1)
 		/* The max size of hash table is PREALLOC_TB_SIZE */
@@ -3542,9 +3542,9 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
 	if (cpa) {
 		ext4_mb_use_group_pa(ac, cpa);
 		ac->ac_criteria = 20;
-		return 1;
+		return true;
 	}
-	return 0;
+	return false;
 }
 
 /*
Signature · 1 line
2.21.0
#15 lore raw

RFC 10/20 ext4: mballoc: Remove EXT4_MB_HINT_GOAL_ONLY and it's related code

In reply to Ritesh Harjani

We don't set EXT4_MB_HINT_GOAL_ONLY flag at any place and from our last during discussion, we don't see a need/use case of it anytime in near future too.
So just kill the flag and all of it's references.
This also adjusts the remaining ac_flags macro values accordingly.

---
 fs/ext4/ext4.h              | 12 +++++-------
 fs/ext4/mballoc.c           | 10 ----------
 include/trace/events/ext4.h |  1 -
 3 files changed, 5 insertions(+), 18 deletions(-)
fs/ext4/ext4.h +5 -7
index 91eb4381cae5..db4fb62c1169 100644
@@ -130,18 +130,16 @@ enum SHIFT_DIRECTION {
 #define EXT4_MB_HINT_NOPREALLOC		0x0040
 /* allocate for locality group */
 #define EXT4_MB_HINT_GROUP_ALLOC	0x0080
-/* allocate goal blocks or none */
-#define EXT4_MB_HINT_GOAL_ONLY		0x0100
 /* goal is meaningful */
-#define EXT4_MB_HINT_TRY_GOAL		0x0200
+#define EXT4_MB_HINT_TRY_GOAL		0x0100
 /* blocks already pre-reserved by delayed allocation */
-#define EXT4_MB_DELALLOC_RESERVED	0x0400
+#define EXT4_MB_DELALLOC_RESERVED	0x0200
 /* We are doing stream allocation */
-#define EXT4_MB_STREAM_ALLOC		0x0800
+#define EXT4_MB_STREAM_ALLOC		0x0400
 /* Use reserved root blocks if needed */
-#define EXT4_MB_USE_ROOT_BLOCKS		0x1000
+#define EXT4_MB_USE_ROOT_BLOCKS		0x0800
 /* Use blocks from reserved pool */
-#define EXT4_MB_USE_RESERVED		0x2000
+#define EXT4_MB_USE_RESERVED		0x1000
 
 struct ext4_allocation_request {
 	/* target inode for block we're allocating */
fs/ext4/mballoc.c +0 -10
index 6e7232fd109e..4d6effe22652 100644
@@ -2157,9 +2157,6 @@ ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
 	if (err || ac->ac_status == AC_STATUS_FOUND)
 		goto out;
 
-	if (unlikely(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY))
-		goto out;
-
 	/*
 	 * ac->ac2_order is set only if the fe_len is a power of 2
 	 * if ac2_order is set we also set criteria to 0 so that we
@@ -3139,10 +3136,6 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
 	if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
 		return;
 
-	/* sometime caller may want exact blocks */
-	if (unlikely(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY))
-		return;
-
 	/* caller may indicate that preallocation isn't
 	 * required (it's a tail, for example) */
 	if (ac->ac_flags & EXT4_MB_HINT_NOPREALLOC)
@@ -4257,9 +4250,6 @@ static void ext4_mb_group_or_file(struct ext4_allocation_context *ac)
 	if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
 		return;
 
-	if (unlikely(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY))
-		return;
-
 	size = ac->ac_o_ex.fe_logical + EXT4_C2B(sbi, ac->ac_o_ex.fe_len);
 	isize = (i_size_read(ac->ac_inode) + ac->ac_sb->s_blocksize - 1)
 		>> bsbits;
include/trace/events/ext4.h +0 -1
index 19c87661eeec..a2a603172f57 100644
@@ -30,7 +30,6 @@ struct partial_cluster;
 	{ EXT4_MB_HINT_DATA,		"HINT_DATA" },		\
 	{ EXT4_MB_HINT_NOPREALLOC,	"HINT_NOPREALLOC" },	\
 	{ EXT4_MB_HINT_GROUP_ALLOC,	"HINT_GRP_ALLOC" },	\
-	{ EXT4_MB_HINT_GOAL_ONLY,	"HINT_GOAL_ONLY" },	\
 	{ EXT4_MB_HINT_TRY_GOAL,	"HINT_TRY_GOAL" },	\
 	{ EXT4_MB_DELALLOC_RESERVED,	"DELALLOC_RESV" },	\
 	{ EXT4_MB_STREAM_ALLOC,		"STREAM_ALLOC" },	\
Signature · 1 line
2.21.0
#16 lore raw

RFC 11/20 ext4: mballoc: Refactor code inside DOUBLE_CHECK into separate function

In reply to Ritesh Harjani

This patch implemets mb_group_bb_bitmap_alloc() and mb_group_bb_bitmap_free() function to remove #ifdef DOUBLE_CHECK macro and it's related code from inside ext4_mb_add_groupinfo()/ext4_mb_release().

There should be no functionality change in this patch.

---
 fs/ext4/mballoc.c | 50 +++++++++++++++++++++++++++++++----------------
 1 file changed, 33 insertions(+), 17 deletions(-)
fs/ext4/mballoc.c +33 -17
index 4d6effe22652..5e59c18c89c0 100644
@@ -540,6 +540,26 @@ static void mb_cmp_bitmaps(struct ext4_buddy *e4b, void *bitmap)
 	}
 }
 
+static void mb_group_bb_bitmap_alloc(struct super_block *sb,
+			struct ext4_group_info *grp, ext4_group_t group)
+{
+	struct buffer_head *bh;
+
+	grp->bb_bitmap = kmalloc(sb->s_blocksize, GFP_NOFS);
+	BUG_ON(grp->bb_bitmap == NULL);
+
+	bh = ext4_read_block_bitmap(sb, group);
+	BUG_ON(IS_ERR_OR_NULL(bh));
+
+	memcpy(grp->bb_bitmap, bh->b_data, sb->s_blocksize);
+	put_bh(bh);
+}
+
+static void mb_group_bb_bitmap_free(struct ext4_group_info *grp)
+{
+	kfree(grp->bb_bitmap);
+}
+
 #else
 static inline void mb_free_blocks_double(struct inode *inode,
 				struct ext4_buddy *e4b, int first, int count)
@@ -555,6 +575,17 @@ static inline void mb_cmp_bitmaps(struct ext4_buddy *e4b, void *bitmap)
 {
 	return;
 }
+
+static inline void mb_group_bb_bitmap_alloc(struct super_block *sb,
+			struct ext4_group_info *grp, ext4_group_t group)
+{
+	return;
+}
+
+static inline void mb_group_bb_bitmap_free(struct ext4_group_info *grp)
+{
+	return;
+}
 #endif
 
 #ifdef AGGRESSIVE_CHECK
@@ -2482,20 +2513,7 @@ int ext4_mb_add_groupinfo(struct super_block *sb, ext4_group_t group,
 	meta_group_info[i]->bb_free_root = RB_ROOT;
 	meta_group_info[i]->bb_largest_free_order = -1;  /* uninit */
 
-#ifdef DOUBLE_CHECK
-	{
-		struct buffer_head *bh;
-		meta_group_info[i]->bb_bitmap =
-			kmalloc(sb->s_blocksize, GFP_NOFS);
-		BUG_ON(meta_group_info[i]->bb_bitmap == NULL);
-		bh = ext4_read_block_bitmap(sb, group);
-		BUG_ON(IS_ERR_OR_NULL(bh));
-		memcpy(meta_group_info[i]->bb_bitmap, bh->b_data,
-			sb->s_blocksize);
-		put_bh(bh);
-	}
-#endif
-
+	mb_group_bb_bitmap_alloc(sb, meta_group_info[i], group);
 	return 0;
 
 exit_group_info:
@@ -2762,9 +2780,7 @@ int ext4_mb_release(struct super_block *sb)
 		for (i = 0; i < ngroups; i++) {
 			cond_resched();
 			grinfo = ext4_get_group_info(sb, i);
-#ifdef DOUBLE_CHECK
-			kfree(grinfo->bb_bitmap);
-#endif
+			mb_group_bb_bitmap_free(grinfo);
 			ext4_lock_group(sb, i);
 			ext4_mb_cleanup_pa(grinfo);
 			ext4_unlock_group(sb, i);
Signature · 1 line
2.21.0
#17 lore raw

RFC 12/20 ext4: mballoc: Fix possible NULL ptr dereference from mb_cmp_bitmaps()

In reply to Ritesh Harjani

Make sure to check for e4b->bd_info->bb_bitmap == NULL, in mb_cmp_bitmaps() and return if NULL, to avoid possible NULL ptr dereference. Similar to how we do this in other ifdef DOUBLE_CHECK functions.

---
 fs/ext4/mballoc.c | 2 ++
 1 file changed, 2 insertions(+)
fs/ext4/mballoc.c +2 -0
index 5e59c18c89c0..e32f3675f962 100644
@@ -522,6 +522,8 @@ static void mb_mark_used_double(struct ext4_buddy *e4b, int first, int count)
 
 static void mb_cmp_bitmaps(struct ext4_buddy *e4b, void *bitmap)
 {
+	if (unlikely(e4b->bd_info->bb_bitmap == NULL))
+		return;
 	if (memcmp(e4b->bd_info->bb_bitmap, bitmap, e4b->bd_sb->s_blocksize)) {
 		unsigned char *b1, *b2;
 		int i;
Signature · 1 line
2.21.0
#18 lore raw

RFC 13/20 ext4: mballoc: Don't BUG if kmalloc or read blk bitmap fail for DOUBLE_CHECK

In reply to Ritesh Harjani

Remove the BUG_ON() logic if kmalloc() or ext4_read_block_bitmap() fails. We should simply mark grp->bb_bitmap as NULL if above happens. In fact ext4_read_block_bitmap() may even return an error in case of resize ioctl. Hence remove this BUG_ON logic (fstests ext4/032 may trigger this).

---
Should we add a ext4_msg() if any of above fails?

---
 fs/ext4/mballoc.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)
fs/ext4/mballoc.c +9 -2
index e32f3675f962..aa22ecf3f827 100644
@@ -548,13 +548,20 @@ static void mb_group_bb_bitmap_alloc(struct super_block *sb,
 	struct buffer_head *bh;
 
 	grp->bb_bitmap = kmalloc(sb->s_blocksize, GFP_NOFS);
-	BUG_ON(grp->bb_bitmap == NULL);
+	if (!grp->bb_bitmap)
+		goto out;
 
 	bh = ext4_read_block_bitmap(sb, group);
-	BUG_ON(IS_ERR_OR_NULL(bh));
+	if (IS_ERR_OR_NULL(bh)) {
+		kfree(grp->bb_bitmap);
+		grp->bb_bitmap = NULL;
+		goto out;
+	}
 
 	memcpy(grp->bb_bitmap, bh->b_data, sb->s_blocksize);
 	put_bh(bh);
+out:
+	return;
 }
 
 static void mb_group_bb_bitmap_free(struct ext4_group_info *grp)
Signature · 1 line
2.21.0
#19 lore raw

RFC 14/20 ext4: balloc: Use task_pid_nr() helper

In reply to Ritesh Harjani

Use task_pid_nr() function instead of current->pid. There should be no functionality change in this patch.

---
 fs/ext4/balloc.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
fs/ext4/balloc.c +3 -2
index a32e5f7b5385..1ba46d87cdf1 100644
@@ -903,10 +903,11 @@ ext4_fsblk_t ext4_inode_to_goal_block(struct inode *inode)
 		return bg_start;
 
 	if (bg_start + EXT4_BLOCKS_PER_GROUP(inode->i_sb) <= last_block)
-		colour = (current->pid % 16) *
+		colour = (task_pid_nr(current) % 16) *
 			(EXT4_BLOCKS_PER_GROUP(inode->i_sb) / 16);
 	else
-		colour = (current->pid % 16) * ((last_block - bg_start) / 16);
+		colour = (task_pid_nr(current) % 16) *
+			((last_block - bg_start) / 16);
 	return bg_start + colour;
 }
 
Signature · 1 line
2.21.0
#20 lore raw

RFC 15/20 ext4: Use BIT() macro for BH_** state bits

In reply to Ritesh Harjani

Simply use BIT() macro for all BH_** state bits instead of open coding it.

There should be no functionality change in this patch.

---
 fs/ext4/ext4.h  | 8 ++++----
 fs/ext4/inode.c | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)
fs/ext4/ext4.h +4 -4
index db4fb62c1169..214102f84a10 100644
@@ -169,10 +169,10 @@ struct ext4_allocation_request {
  * well as to store the information returned by ext4_map_blocks().  It
  * takes less room on the stack than a struct buffer_head.
  */
-#define EXT4_MAP_NEW		(1 << BH_New)
-#define EXT4_MAP_MAPPED		(1 << BH_Mapped)
-#define EXT4_MAP_UNWRITTEN	(1 << BH_Unwritten)
-#define EXT4_MAP_BOUNDARY	(1 << BH_Boundary)
+#define EXT4_MAP_NEW		BIT(BH_New)
+#define EXT4_MAP_MAPPED		BIT(BH_Mapped)
+#define EXT4_MAP_UNWRITTEN	BIT(BH_Unwritten)
+#define EXT4_MAP_BOUNDARY	BIT(BH_Boundary)
 #define EXT4_MAP_FLAGS		(EXT4_MAP_NEW | EXT4_MAP_MAPPED |\
 				 EXT4_MAP_UNWRITTEN | EXT4_MAP_BOUNDARY)
 
fs/ext4/inode.c +2 -2
index 2a4aae6acdcb..e294abeb7f03 100644
@@ -2078,7 +2078,7 @@ static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
 	return err;
 }
 
-#define BH_FLAGS ((1 << BH_Unwritten) | (1 << BH_Delay))
+#define BH_FLAGS (BIT(BH_Unwritten) | BIT(BH_Delay))
 
 /*
  * mballoc gives us at most this number of blocks...
@@ -2358,7 +2358,7 @@ static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
 	dioread_nolock = ext4_should_dioread_nolock(inode);
 	if (dioread_nolock)
 		get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
-	if (map->m_flags & (1 << BH_Delay))
+	if (map->m_flags & BIT(BH_Delay))
 		get_blocks_flags |= EXT4_GET_BLOCKS_DELALLOC_RESERVE;
 
 	err = ext4_map_blocks(handle, inode, map, get_blocks_flags);
Signature · 1 line
2.21.0
#21 lore raw

RFC 16/20 ext4: Improve ext_debug() msg in case of block allocation failure

In reply to Ritesh Harjani

ext4_map_blocks() has ext_debug msg early at the start of function. We also get ext_debug msg if we could allocate a block from ext4_ext_map_blocks(). But there is no ext_debug() msg in case of block allocation failure. So add one along with error code.

Also add more info in ext_debug() msg like how many blocks were allocated v/s how many were requested in ext4_ext_map_blocks().

---
 fs/ext4/extents.c | 4 ++--
 fs/ext4/inode.c   | 4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)
fs/ext4/extents.c +2 -2
index f2b577b315a0..461600c07316 100644
@@ -4218,10 +4218,10 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
 	newblock = ext4_mb_new_blocks(handle, &ar, &err);
 	if (!newblock)
 		goto out2;
-	ext_debug("allocate new block: goal %llu, found %llu/%u\n",
-		  ar.goal, newblock, allocated);
 	allocated_clusters = ar.len;
 	ar.len = EXT4_C2B(sbi, ar.len) - offset;
+	ext_debug("allocate new block: goal %llu, found %llu/%u, requested %u\n",
+		  ar.goal, newblock, ar.len, allocated);
 	if (ar.len > allocated)
 		ar.len = allocated;
 
fs/ext4/inode.c +4 -0
index e294abeb7f03..5f120af22d48 100644
@@ -726,6 +726,10 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
 				return ret;
 		}
 	}
+
+	if (retval < 0)
+		ext_debug("failed for inode %lu with err %d\n",
+			  inode->i_ino, retval);
 	return retval;
 }
 
Signature · 1 line
2.21.0
#22 lore raw

RFC 17/20 ext4: Replace EXT_DEBUG with __maybe_unused in ext4_ext_handle_unwritten_extents()

In reply to Ritesh Harjani

Replace EXT_DEBUG with __maybe_unused from inside
ext4_ext_handle_unwritten_extents() function.

There should be no functionality change in this patch.

---
 fs/ext4/extents.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
fs/ext4/extents.c +1 -3
index 461600c07316..dc980fbc49aa 100644
@@ -3794,9 +3794,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, struct inode *inode,
 			struct ext4_ext_path **ppath, int flags,
 			unsigned int allocated, ext4_fsblk_t newblock)
 {
-#ifdef EXT_DEBUG
-	struct ext4_ext_path *path = *ppath;
-#endif
+	struct ext4_ext_path __maybe_unused *path = *ppath;
 	int ret = 0;
 	int err = 0;
 
Signature · 1 line
2.21.0
#23 lore raw

RFC 18/20 ext4: mballoc: Make mb_debug() implementation to use pr_debug()

In reply to Ritesh Harjani

mb_debug() msg had only 1 control level for all type of msgs. And if we enable mballoc_debug then all of those msgs would be enabled. Instead of adding multiple debug levels for mb_debug() msgs, use pr_debug() with which we could have finer control to print msgs at all of different levels (i.e. at file, func, line no.).

Also add process name/pid, superblk id, and other info in mb_debug() msg. This also kills the mballoc_debug module parameter, since it is not needed any more.

---
 fs/ext4/Kconfig   |   3 +-
 fs/ext4/mballoc.c | 106 +++++++++++++++++++++-------------------------
 fs/ext4/mballoc.h |  16 +++----
 3 files changed, 56 insertions(+), 69 deletions(-)
fs/ext4/Kconfig +1 -2
index 2a592e38cdfe..02376ddb0cb5 100644
@@ -99,8 +99,7 @@ config EXT4_DEBUG
 	  Enables run-time debugging support for the ext4 filesystem.
 
 	  If you select Y here, then you will be able to turn on debugging
-	  with a command such as:
-		echo 1 > /sys/module/ext4/parameters/mballoc_debug
+	  using dynamic debug control for mb_debug() msgs.
 
 config EXT4_KUNIT_TESTS
 	tristate "KUnit tests for ext4"
fs/ext4/mballoc.c +49 -57

320 lines not on this page · raw message

fs/ext4/mballoc.h +6 -10
index 88c98f17e3d9..6b4d17c2935d 100644
@@ -24,19 +24,15 @@
 #include "ext4.h"
 
 /*
+ * mb_debug() dynamic printk msgs could be used to debug mballoc code.
  */
 #ifdef CONFIG_EXT4_DEBUG
-extern ushort ext4_mballoc_debug;
-
-#define mb_debug(n, fmt, ...)	                                        \
-do {									\
-	if ((n) <= ext4_mballoc_debug) {				\
-		printk(KERN_DEBUG "(%s, %d): %s: " fmt,			\
-		       __FILE__, __LINE__, __func__, ##__VA_ARGS__);	\
-	}								\
-} while (0)
+#define mb_debug(sb, fmt, ...)						\
+	pr_debug("[%s/%d] EXT4-fs (%s): (%s, %d): %s: " fmt,		\
+		current->comm, task_pid_nr(current), sb->s_id,		\
+	       __FILE__, __LINE__, __func__, ##__VA_ARGS__)
 #else
-#define mb_debug(n, fmt, ...)	no_printk(fmt, ##__VA_ARGS__)
+#define mb_debug(sb, fmt, ...)	no_printk(fmt, ##__VA_ARGS__)
 #endif
 
 #define EXT4_MB_HISTORY_ALLOC		1	/* allocation */
Signature · 1 line
2.21.0
#24 lore raw

RFC 19/20 ext4: Make ext_debug() implementation to use pr_debug()

In reply to Ritesh Harjani

ext_debug() msgs could be helpful, provided those could be enabled without recompiling kernel and also if we could selectively enable only required prints for case by case debugging.

So make ext_debug() implementation use pr_debug(). Also change ext_debug() to be defined with CONFIG_EXT4_DEBUG. So EXT_DEBUG macro now mostly remain for below 3 functions. ext4_ext_show_path/leaf/move() (whose print msgs use ext_debug() which again could be dynamically enabled using pr_debug())

This also changes the ext_debug() to take inode as a parameter to add inode no. in all of it's msgs.
Prints additional info like process name / pid, superblock id etc. This also removes any explicit function names passed in ext_debug(). Since ext_debug() on it's own prints file, func and line no.

---
 fs/ext4/Kconfig   |   2 +-
 fs/ext4/ext4.h    |  18 ++++--
 fs/ext4/extents.c | 144 ++++++++++++++++++++++------------------------
 fs/ext4/inode.c   |  11 ++--
 4 files changed, 87 insertions(+), 88 deletions(-)
fs/ext4/Kconfig +1 -1
index 02376ddb0cb5..cf9e430514c4 100644
@@ -99,7 +99,7 @@ config EXT4_DEBUG
 	  Enables run-time debugging support for the ext4 filesystem.
 
 	  If you select Y here, then you will be able to turn on debugging
-	  using dynamic debug control for mb_debug() msgs.
+	  using dynamic debug control for mb_debug() / ext_debug() msgs.
 
 config EXT4_KUNIT_TESTS
 	tristate "KUnit tests for ext4"
fs/ext4/ext4.h +13 -5
index 214102f84a10..1cc7a43fbc11 100644
@@ -80,14 +80,22 @@
 #define ext4_debug(fmt, ...)	no_printk(fmt, ##__VA_ARGS__)
 #endif
 
+ /*
+  * Turn on EXT_DEBUG to enable ext4_ext_show_path/leaf/move in extents.c
+  */
+#define EXT_DEBUG__
+
 /*
- * Turn on EXT_DEBUG to get lots of info about extents operations.
+ * Dynamic printk for controlled extents debugging.
  */
-#define EXT_DEBUG__
-#ifdef EXT_DEBUG
-#define ext_debug(fmt, ...)	printk(fmt, ##__VA_ARGS__)
+#ifdef CONFIG_EXT4_DEBUG
+#define ext_debug(ino, fmt, ...)					\
+	pr_debug("[%s/%d] EXT4-fs (%s): ino %lu: (%s, %d): %s:" fmt,	\
+		 current->comm, task_pid_nr(current),			\
+		 ino->i_sb->s_id, ino->i_ino, __FILE__, __LINE__,	\
+		 __func__, ##__VA_ARGS__)
 #else
-#define ext_debug(fmt, ...)	no_printk(fmt, ##__VA_ARGS__)
+#define ext_debug(ino, fmt, ...)	no_printk(fmt, ##__VA_ARGS__)
 #endif
 
 /* data type for block offset of block group */
fs/ext4/extents.c +69 -75

452 lines not on this page · raw message

fs/ext4/inode.c +4 -7
index 5f120af22d48..a39fea85ca59 100644
@@ -493,9 +493,8 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
 #endif
 
 	map->m_flags = 0;
-	ext_debug("ext4_map_blocks(): inode %lu, flag %d, max_blocks %u,"
-		  "logical block %lu\n", inode->i_ino, flags, map->m_len,
-		  (unsigned long) map->m_lblk);
+	ext_debug(inode, "flag 0x%x, max_blocks %u, logical block %lu\n",
+		  flags, map->m_len, (unsigned long) map->m_lblk);
 
 	/*
 	 * ext4_map_blocks returns an int, and m_len is an unsigned int
@@ -728,8 +727,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
 	}
 
 	if (retval < 0)
-		ext_debug("failed for inode %lu with err %d\n",
-			  inode->i_ino, retval);
+		ext_debug(inode, "failed with err %d\n", retval);
 	return retval;
 }
 
@@ -1685,8 +1683,7 @@ static int ext4_da_map_blocks(struct inode *inode, sector_t iblock,
 		invalid_block = ~0;
 
 	map->m_flags = 0;
-	ext_debug("ext4_da_map_blocks(): inode %lu, max_blocks %u,"
-		  "logical block %lu\n", inode->i_ino, map->m_len,
+	ext_debug(inode, "max_blocks %u, logical block %lu\n", map->m_len,
 		  (unsigned long) map->m_lblk);
 
 	/* Lookup extent status tree firstly */
Signature · 1 line
2.21.0
#25 lore raw

RFC 20/20 ext4: Add process name and pid in ext4_msg()

In reply to Ritesh Harjani

This adds process name and pid for ext4_msg().
I found this to be useful. For e.g. below print gives more info about process name and pid.

[ 7671.131912]  [mount/12543] EXT4-fs (dm-0): mounted filesystem with ordered data mode. Opts: acl,user_xattr
---
 fs/ext4/super.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/ext4/super.c +2 -1
index bf5fcb477f66..5067a47f4f46 100644
@@ -756,7 +756,8 @@ void __ext4_msg(struct super_block *sb,
 	va_start(args, fmt);
 	vaf.fmt = fmt;
 	vaf.va = &args;
-	printk("%sEXT4-fs (%s): %pV\n", prefix, sb->s_id, &vaf);
+	printk("%s [%s/%d] EXT4-fs (%s): %pV\n", prefix, current->comm,
+		task_pid_nr(current), sb->s_id, &vaf);
 	va_end(args);
 }
 
Signature · 1 line
2.21.0