summaryrefslogtreecommitdiff
path: root/fs/bcachefs/buckets_waiting_for_journal.c
blob: f3774e30b5cdf12b4bf85c6a9fc8dfc9e367360a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// SPDX-License-Identifier: GPL-2.0

#include "bcachefs.h"
#include "buckets_waiting_for_journal.h"
#include <linux/hash.h>
#include <linux/random.h>

static inline struct bucket_hashed *
bucket_hash(struct buckets_waiting_for_journal_table *t,
	    unsigned hash_seed_idx, u64 dev_bucket)
{
	return t->d + hash_64(dev_bucket ^ t->hash_seeds[hash_seed_idx], t->bits);
}

static void bucket_table_init(struct buckets_waiting_for_journal_table *t, size_t bits)
{
	unsigned i;

	t->bits = bits;
	for (i = 0; i < ARRAY_SIZE(t->hash_seeds); i++)
		get_random_bytes(&t->hash_seeds[i], sizeof(t->hash_seeds[i]));
	memset(t->d, 0, sizeof(t->d[0]) << t->bits);
}

bool bch2_bucket_needs_journal_commit(struct buckets_waiting_for_journal *b,
				      u64 flushed_seq,
				      unsigned dev, u64 bucket)
{
	struct buckets_waiting_for_journal_table *t;
	u64 dev_bucket = (u64) dev << 56 | bucket;
	bool ret = false;
	unsigned i;

	mutex_lock(&b->lock);
	t = b->t;

	for (i = 0; i < ARRAY_SIZE(t->hash_seeds); i++) {
		struct bucket_hashed *h = bucket_hash(t, i, dev_bucket);

		if (h->dev_bucket == dev_bucket) {
			ret = h->journal_seq > flushed_seq;
			break;
		}
	}

	mutex_unlock(&b->lock);

	return ret;
}

static bool bucket_table_insert(struct buckets_waiting_for_journal_table *t,
				struct bucket_hashed *new,
				u64 flushed_seq)
{
	struct bucket_hashed *last_evicted = NULL;
	unsigned tries, i;

	for (tries = 0; tries < 10; tries++) {
		struct bucket_hashed *old, *victim = NULL;

		for (i = 0; i < ARRAY_SIZE(t->hash_seeds); i++) {
			old = bucket_hash(t, i, new->dev_bucket);

			if (old->dev_bucket == new->dev_bucket ||
			    old->journal_seq <= flushed_seq) {
				*old = *new;
				return true;
			}

			if (last_evicted != old)
				victim = old;
		}

		/* hashed to same slot 3 times: */
		if (!victim)
			break;

		/* Failed to find an empty slot: */
		swap(*new, *victim);
		last_evicted = victim;
	}

	return false;
}

int bch2_set_bucket_needs_journal_commit(struct buckets_waiting_for_journal *b,
					 u64 flushed_seq,
					 unsigned dev, u64 bucket,
					 u64 journal_seq)
{
	struct buckets_waiting_for_journal_table *t, *n;
	struct bucket_hashed tmp, new = {
		.dev_bucket	= (u64) dev << 56 | bucket,
		.journal_seq	= journal_seq,
	};
	size_t i, size, new_bits, nr_elements = 1, nr_rehashes = 0;
	int ret = 0;

	mutex_lock(&b->lock);

	if (likely(bucket_table_insert(b->t, &new, flushed_seq)))
		goto out;

	t = b->t;
	size = 1UL << t->bits;
	for (i = 0; i < size; i++)
		nr_elements += t->d[i].journal_seq > flushed_seq;

	new_bits = t->bits + (nr_elements * 3 > size);

	n = kvmalloc(sizeof(*n) + (sizeof(n->d[0]) << new_bits), GFP_KERNEL);
	if (!n) {
		ret = -ENOMEM;
		goto out;
	}

retry_rehash:
	nr_rehashes++;
	bucket_table_init(n, new_bits);

	tmp = new;
	BUG_ON(!bucket_table_insert(n, &tmp, flushed_seq));

	for (i = 0; i < 1UL << t->bits; i++) {
		if (t->d[i].journal_seq <= flushed_seq)
			continue;

		tmp = t->d[i];
		if (!bucket_table_insert(n, &tmp, flushed_seq))
			goto retry_rehash;
	}

	b->t = n;
	kvfree(t);

	pr_debug("took %zu rehashes, table at %zu/%zu elements",
		 nr_rehashes, nr_elements, 1UL << b->t->bits);
out:
	mutex_unlock(&b->lock);

	return ret;
}

void bch2_fs_buckets_waiting_for_journal_exit(struct bch_fs *c)
{
	struct buckets_waiting_for_journal *b = &c->buckets_waiting_for_journal;

	kvfree(b->t);
}

#define INITIAL_TABLE_BITS		3

int bch2_fs_buckets_waiting_for_journal_init(struct bch_fs *c)
{
	struct buckets_waiting_for_journal *b = &c->buckets_waiting_for_journal;

	mutex_init(&b->lock);

	b->t = kvmalloc(sizeof(*b->t) +
			(sizeof(b->t->d[0]) << INITIAL_TABLE_BITS), GFP_KERNEL);
	if (!b->t)
		return -ENOMEM;

	bucket_table_init(b->t, INITIAL_TABLE_BITS);
	return 0;
}