mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	 f5241e4127
			
		
	
	
		f5241e4127
		
	
	
	
	
		
			
			Factor out the debug code for rw filesystem refs into a small library. In release mode an enumerated ref is a normal percpu refcount, but in debug mode all enumerated users of the ref get their own atomic_long_t ref - making it much easier to chase down refcount usage bugs for when a refcount has many users. For debugging, we have enumerated_ref_to_text(), which prints the current value of each different user. Additionally, in debug mode enumerated_ref_stop() has a 10 second timeout, after which it will dump outstanding refcounts. Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
		
			
				
	
	
		
			144 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| 
 | |
| #include "bcachefs.h"
 | |
| #include "enumerated_ref.h"
 | |
| #include "util.h"
 | |
| 
 | |
| #include <linux/completion.h>
 | |
| 
 | |
| #ifdef ENUMERATED_REF_DEBUG
 | |
| void enumerated_ref_get(struct enumerated_ref *ref, unsigned idx)
 | |
| {
 | |
| 	BUG_ON(idx >= ref->nr);
 | |
| 	atomic_long_inc(&ref->refs[idx]);
 | |
| }
 | |
| 
 | |
| bool __enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx)
 | |
| {
 | |
| 	BUG_ON(idx >= ref->nr);
 | |
| 	return atomic_long_inc_not_zero(&ref->refs[idx]);
 | |
| }
 | |
| 
 | |
| bool enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx)
 | |
| {
 | |
| 	BUG_ON(idx >= ref->nr);
 | |
| 	return !ref->dying &&
 | |
| 		atomic_long_inc_not_zero(&ref->refs[idx]);
 | |
| }
 | |
| 
 | |
| void enumerated_ref_put(struct enumerated_ref *ref, unsigned idx)
 | |
| {
 | |
| 	BUG_ON(idx >= ref->nr);
 | |
| 	long v = atomic_long_dec_return(&ref->refs[idx]);
 | |
| 
 | |
| 	BUG_ON(v < 0);
 | |
| 	if (v)
 | |
| 		return;
 | |
| 
 | |
| 	for (unsigned i = 0; i < ref->nr; i++)
 | |
| 		if (atomic_long_read(&ref->refs[i]))
 | |
| 			return;
 | |
| 
 | |
| 	if (ref->stop_fn)
 | |
| 		ref->stop_fn(ref);
 | |
| 	complete(&ref->stop_complete);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #ifndef ENUMERATED_REF_DEBUG
 | |
| static void enumerated_ref_kill_cb(struct percpu_ref *percpu_ref)
 | |
| {
 | |
| 	struct enumerated_ref *ref =
 | |
| 		container_of(percpu_ref, struct enumerated_ref, ref);
 | |
| 
 | |
| 	if (ref->stop_fn)
 | |
| 		ref->stop_fn(ref);
 | |
| 	complete(&ref->stop_complete);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void enumerated_ref_stop_async(struct enumerated_ref *ref)
 | |
| {
 | |
| 	reinit_completion(&ref->stop_complete);
 | |
| 
 | |
| #ifndef ENUMERATED_REF_DEBUG
 | |
| 	percpu_ref_kill(&ref->ref);
 | |
| #else
 | |
| 	ref->dying = true;
 | |
| 	for (unsigned i = 0; i < ref->nr; i++)
 | |
| 		enumerated_ref_put(ref, i);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void enumerated_ref_stop(struct enumerated_ref *ref,
 | |
| 			 const char * const names[])
 | |
| {
 | |
| 	enumerated_ref_stop_async(ref);
 | |
| 	while (!wait_for_completion_timeout(&ref->stop_complete, HZ * 10)) {
 | |
| 		struct printbuf buf = PRINTBUF;
 | |
| 
 | |
| 		prt_str(&buf, "Waited for 10 seconds to shutdown enumerated ref\n");
 | |
| 		prt_str(&buf, "Outstanding refs:\n");
 | |
| 		enumerated_ref_to_text(&buf, ref, names);
 | |
| 		printk(KERN_ERR "%s", buf.buf);
 | |
| 		printbuf_exit(&buf);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void enumerated_ref_start(struct enumerated_ref *ref)
 | |
| {
 | |
| #ifndef ENUMERATED_REF_DEBUG
 | |
| 	percpu_ref_reinit(&ref->ref);
 | |
| #else
 | |
| 	ref->dying = false;
 | |
| 	for (unsigned i = 0; i < ref->nr; i++) {
 | |
| 		BUG_ON(atomic_long_read(&ref->refs[i]));
 | |
| 		atomic_long_inc(&ref->refs[i]);
 | |
| 	}
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void enumerated_ref_exit(struct enumerated_ref *ref)
 | |
| {
 | |
| #ifndef ENUMERATED_REF_DEBUG
 | |
| 	percpu_ref_exit(&ref->ref);
 | |
| #else
 | |
| 	kfree(ref->refs);
 | |
| 	ref->refs = NULL;
 | |
| 	ref->nr = 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| int enumerated_ref_init(struct enumerated_ref *ref, unsigned nr,
 | |
| 			void (*stop_fn)(struct enumerated_ref *))
 | |
| {
 | |
| 	init_completion(&ref->stop_complete);
 | |
| 	ref->stop_fn = stop_fn;
 | |
| 
 | |
| #ifndef ENUMERATED_REF_DEBUG
 | |
| 	return percpu_ref_init(&ref->ref, enumerated_ref_kill_cb,
 | |
| 			    PERCPU_REF_INIT_DEAD, GFP_KERNEL);
 | |
| #else
 | |
| 	ref->refs = kzalloc(sizeof(ref->refs[0]) * nr, GFP_KERNEL);
 | |
| 	if (!ref->refs)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	ref->nr = nr;
 | |
| 	return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void enumerated_ref_to_text(struct printbuf *out,
 | |
| 			    struct enumerated_ref *ref,
 | |
| 			    const char * const names[])
 | |
| {
 | |
| #ifdef ENUMERATED_REF_DEBUG
 | |
| 	bch2_printbuf_tabstop_push(out, 32);
 | |
| 
 | |
| 	for (unsigned i = 0; i < ref->nr; i++)
 | |
| 		prt_printf(out, "%s\t%li\n", names[i],
 | |
| 			   atomic_long_read(&ref->refs[i]));
 | |
| #else
 | |
| 	prt_str(out, "(not in debug mode)\n");
 | |
| #endif
 | |
| }
 |