xref: /haiku/src/system/kernel/fs/EntryCache.cpp (revision 2cad94c1c30b6223ad8c08710b26e071d32e9979)
1 /*
2  * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "EntryCache.h"
8 
9 #include <new>
10 
11 
12 static const int32 kEntriesPerGeneration = 1024;
13 
14 static const int32 kEntryNotInArray = -1;
15 static const int32 kEntryRemoved = -2;
16 
17 
18 // #pragma mark - EntryCacheGeneration
19 
20 
21 EntryCacheGeneration::EntryCacheGeneration()
22 	:
23 	next_index(0),
24 	entries(NULL)
25 {
26 }
27 
28 
29 EntryCacheGeneration::~EntryCacheGeneration()
30 {
31 	delete[] entries;
32 }
33 
34 
35 status_t
36 EntryCacheGeneration::Init()
37 {
38 	entries = new(std::nothrow) EntryCacheEntry*[kEntriesPerGeneration];
39 	if (entries == NULL)
40 		return B_NO_MEMORY;
41 
42 	memset(entries, 0, sizeof(EntryCacheEntry*) * kEntriesPerGeneration);
43 
44 	return B_OK;
45 }
46 
47 
48 // #pragma mark - EntryCache
49 
50 
51 EntryCache::EntryCache()
52 	:
53 	fCurrentGeneration(0)
54 {
55 	rw_lock_init(&fLock, "entry cache");
56 
57 	new(&fEntries) EntryTable;
58 }
59 
60 
61 EntryCache::~EntryCache()
62 {
63 	// delete entries
64 	EntryCacheEntry* entry = fEntries.Clear(true);
65 	while (entry != NULL) {
66 		EntryCacheEntry* next = entry->hash_link;
67 		free(entry);
68 		entry = next;
69 	}
70 
71 	rw_lock_destroy(&fLock);
72 }
73 
74 
75 status_t
76 EntryCache::Init()
77 {
78 	status_t error = fEntries.Init();
79 	if (error != B_OK)
80 		return error;
81 
82 	for (int32 i = 0; i < kGenerationCount; i++) {
83 		error = fGenerations[i].Init();
84 		if (error != B_OK)
85 			return error;
86 	}
87 
88 	return B_OK;
89 }
90 
91 
92 status_t
93 EntryCache::Add(ino_t dirID, const char* name, ino_t nodeID, bool missing)
94 {
95 	EntryCacheKey key(dirID, name);
96 
97 	WriteLocker _(fLock);
98 
99 	EntryCacheEntry* entry = fEntries.Lookup(key);
100 	if (entry != NULL) {
101 		entry->node_id = nodeID;
102 		entry->missing = missing;
103 		if (entry->generation != fCurrentGeneration) {
104 			if (entry->index >= 0) {
105 				fGenerations[entry->generation].entries[entry->index] = NULL;
106 				_AddEntryToCurrentGeneration(entry);
107 			}
108 		}
109 		return B_OK;
110 	}
111 
112 	entry = (EntryCacheEntry*)malloc(sizeof(EntryCacheEntry) + strlen(name));
113 	if (entry == NULL)
114 		return B_NO_MEMORY;
115 
116 	entry->node_id = nodeID;
117 	entry->dir_id = dirID;
118 	entry->missing = missing;
119 	entry->generation = fCurrentGeneration;
120 	entry->index = kEntryNotInArray;
121 	strcpy(entry->name, name);
122 
123 	fEntries.Insert(entry);
124 
125 	_AddEntryToCurrentGeneration(entry);
126 
127 	return B_OK;
128 }
129 
130 
131 status_t
132 EntryCache::Remove(ino_t dirID, const char* name)
133 {
134 	EntryCacheKey key(dirID, name);
135 
136 	WriteLocker writeLocker(fLock);
137 
138 	EntryCacheEntry* entry = fEntries.Lookup(key);
139 	if (entry == NULL)
140 		return B_ENTRY_NOT_FOUND;
141 
142 	fEntries.Remove(entry);
143 
144 	if (entry->index >= 0) {
145 		// remove the entry from its generation and delete it
146 		fGenerations[entry->generation].entries[entry->index] = NULL;
147 		free(entry);
148 	} else {
149 		// We can't free it, since another thread is about to try to move it
150 		// to another generation. We mark it removed and the other thread will
151 		// take care of deleting it.
152 		entry->index = kEntryRemoved;
153 	}
154 
155 	return B_OK;
156 }
157 
158 
159 bool
160 EntryCache::Lookup(ino_t dirID, const char* name, ino_t& _nodeID,
161 	bool& _missing)
162 {
163 	EntryCacheKey key(dirID, name);
164 
165 	ReadLocker readLocker(fLock);
166 
167 	EntryCacheEntry* entry = fEntries.Lookup(key);
168 	if (entry == NULL)
169 		return false;
170 
171 	int32 oldGeneration = atomic_get_and_set(&entry->generation,
172 			fCurrentGeneration);
173 	if (oldGeneration == fCurrentGeneration || entry->index < 0) {
174 		// The entry is already in the current generation or is being moved to
175 		// it by another thread.
176 		_nodeID = entry->node_id;
177 		_missing = entry->missing;
178 		return true;
179 	}
180 
181 	// remove from old generation array
182 	fGenerations[oldGeneration].entries[entry->index] = NULL;
183 	entry->index = kEntryNotInArray;
184 
185 	// add to the current generation
186 	int32 index = atomic_add(&fGenerations[oldGeneration].next_index, 1);
187 	if (index < kEntriesPerGeneration) {
188 		fGenerations[fCurrentGeneration].entries[index] = entry;
189 		entry->index = index;
190 		_nodeID = entry->node_id;
191 		_missing = entry->missing;
192 		return true;
193 	}
194 
195 	// The current generation is full, so we probably need to clear the oldest
196 	// one to make room. We need the write lock for that.
197 	readLocker.Unlock();
198 	WriteLocker writeLocker(fLock);
199 
200 	if (entry->index == kEntryRemoved) {
201 		// the entry has been removed in the meantime
202 		free(entry);
203 		return false;
204 	}
205 
206 	_AddEntryToCurrentGeneration(entry);
207 
208 	_nodeID = entry->node_id;
209 	_missing = entry->missing;
210 	return true;
211 }
212 
213 
214 const char*
215 EntryCache::DebugReverseLookup(ino_t nodeID, ino_t& _dirID)
216 {
217 	for (EntryTable::Iterator it = fEntries.GetIterator();
218 			EntryCacheEntry* entry = it.Next();) {
219 		if (nodeID == entry->node_id && strcmp(entry->name, ".") != 0
220 				&& strcmp(entry->name, "..") != 0) {
221 			_dirID = entry->dir_id;
222 			return entry->name;
223 		}
224 	}
225 
226 	return NULL;
227 }
228 
229 
230 void
231 EntryCache::_AddEntryToCurrentGeneration(EntryCacheEntry* entry)
232 {
233 	// the generation might not be full yet
234 	int32 index = fGenerations[fCurrentGeneration].next_index++;
235 	if (index < kEntriesPerGeneration) {
236 		fGenerations[fCurrentGeneration].entries[index] = entry;
237 		entry->generation = fCurrentGeneration;
238 		entry->index = index;
239 		return;
240 	}
241 
242 	// we have to clear the oldest generation
243 	int32 newGeneration = (fCurrentGeneration + 1) % kGenerationCount;
244 	for (int32 i = 0; i < kEntriesPerGeneration; i++) {
245 		EntryCacheEntry* otherEntry = fGenerations[newGeneration].entries[i];
246 		if (otherEntry == NULL)
247 			continue;
248 
249 		fGenerations[newGeneration].entries[i] = NULL;
250 		fEntries.Remove(otherEntry);
251 		free(otherEntry);
252 	}
253 
254 	// set the new generation and add the entry
255 	fCurrentGeneration = newGeneration;
256 	fGenerations[newGeneration].next_index = 1;
257 	fGenerations[newGeneration].entries[0] = entry;
258 	entry->generation = newGeneration;
259 	entry->index = 0;
260 }
261