1 module memutils.scoped;
2 
3 import core.thread : Fiber;
4 import memutils.constants;
5 import memutils.allocators;
6 import memutils.pool;
7 import memutils.utils;
8 import memutils.vector;
9 import memutils.refcounted;
10 import memutils.unique;
11 import memutils.hashmap;
12 import memutils.freelist;
13 import memutils.memory;
14 import std.traits : hasElaborateDestructor, isArray;
15 import std.algorithm : min;
16 import std.exception;
17 import core.exception;
18 
19 alias ScopedPool = RefCounted!ScopedPoolImpl;
20 
21 final class ScopedPoolImpl {
22 	// TODO: Use a name for debugging?
23 
24 	int id;
25 	/// Initializes a scoped pool with max_mem
26 	/// max_mem doesn't do anything at the moment
27 	this(size_t max_mem = 0) {
28 		PoolStack.push();
29 		id = PoolStack.top.id;
30 	}
31 
32 	~this() {
33 		debug if(id != PoolStack.top.id) unfreeze();
34 		PoolStack.pop();
35 	}
36 
37 	/// Use only if ScopedPool is the highest on stack.
38 	void freeze() {
39 		enforce(id == PoolStack.top.id);
40 		enforce(PoolStack.freeze(1) == 1, "Failed to freeze pool");
41 	}
42 
43 	void unfreeze() {
44 		enforce(PoolStack.unfreeze(1) == 1, "Failed to unfreeze pool");
45 		enforce(id == PoolStack.top.id);
46 	}
47 }
48 
49 T alloc(T, ARGS...)(auto ref ARGS args)
50 	if (is(T == class) || is(T == interface) || __traits(isAbstractClass, T))
51 {
52 	T ret;
53 	
54 	if (!PoolStack.empty) {
55 		ret = ObjectAllocator!(T, PoolStack).alloc(args);
56 		
57 		// Add destructor to pool
58 		static if (hasElaborateDestructor!T || __traits(hasMember, T, "__dtor") ) 
59 			PoolStack.top().onDestroy(&ret.__dtor);
60 	}
61 	else {
62 		ret = new T(args);
63 	}
64 	
65 	return ret;
66 }
67 
68 T* alloc(T, ARGS...)(auto ref ARGS args)
69 	if (!(is(T == class) || is(T == interface) || __traits(isAbstractClass, T)))
70 {
71 	T* ret;
72 	
73 	if (!PoolStack.empty) {
74 		ret = ObjectAllocator!(T, PoolStack).alloc(args);
75 		
76 		// Add destructor to pool
77 		static if (hasElaborateDestructor!T || __traits(hasMember, T, "__dtor") ) 
78 			PoolStack.top.onDestroy(&ret.__dtor);
79 		
80 	}
81 	else {
82 		ret = new T(args);
83 	}
84 	
85 	return ret;
86 }
87 
88 /// arrays
89 auto alloc(T)(size_t n)
90 	if (isArray!T)
91 {
92 	import std.range : ElementType;
93 	
94 	T ret;
95 	if (!PoolStack.empty) {
96 		ret = allocArray!(ElementType!T, PoolStack)(n);
97 		registerPoolArray(ret);
98 	}
99 	else {
100 		ret = new T(n);
101 	}
102 	return ret;
103 }
104 
105 auto realloc(T)(ref T arr, size_t n)
106 	if (isArray!T)
107 {
108 	import std.range : ElementType;
109 	T ret;
110 	if (!PoolStack.empty) {
111 		scope(exit) arr = null;
112 		ret = reallocArray!(ElementType!T, PoolStack)(arr, n);
113 		reregisterPoolArray(arr, ret);
114 	}
115 	else {
116 		arr.length = n;
117 		ret = arr;
118 	}
119 }
120 
121 
122 struct PoolStack {
123 static:
124 	@property bool empty() { return m_tstack.empty && m_fstack.empty; }
125 
126 	/// returns the most recent unfrozen pool, null if none available
127 	@property ManagedPool top() {
128 		if (Fiber.getThis() && !m_fstack.empty) {
129 			return m_fstack.top;
130 		}
131 		return m_tstack.top;
132 	}
133 
134 	/// creates a new pool as the fiber stack top or the thread stack top
135 	void push() {
136 		//logTrace("Pushing PoolStack");
137 		if (Fiber.getThis())
138 			return m_fstack.push();
139 		m_tstack.push();
140 		//logTrace("Pushed ThreadStack");
141 	}
142 
143 	/// destroy the most recent pool and free all its resources, calling destructors
144 	/// if you're in a fiber, search for stack top in the fiber stack or the fiber freezer and destroy it.
145 	/// otherwise, search in the thread stack or the thread freezer and destroy it.
146 	void pop() {
147 		//logTrace("Pop PoolStack");
148 		if (Fiber.getThis() && (!m_fstack.empty || !m_ffreezer.empty))
149 		{
150 			//logTrace("Pop FiberStack");
151 			if (m_fstack.hasTop)
152 				return m_fstack.pop();
153 			//else
154 			auto ret = m_ffreezer.pop(1);
155 			//logTrace("Pop ThreadStack instead");
156 			m_fstack.cnt--;
157 			return;
158 		}
159 		// else
160 		auto top = m_tstack.top;
161 		//assert(top, "Can't find a pool to pop");
162 		//logTrace("Pop ThreadStack");
163 		if (m_tstack.hasTop)
164 			return m_tstack.pop();
165 		//logTrace("Doesn't have top?");
166 		//else
167 		auto ret = m_tfreezer.pop(1);
168 
169 		m_tstack.cnt--;
170 		//logTrace("Destroyign ", ret.back.id);
171 
172 	}
173 
174 	void disable() {
175 		freeze(m_tstack.length + m_fstack.length);
176 	}
177 
178 	void enable() {
179 		unfreeze(m_ffreezer.length + m_tfreezer.length);
180 	}
181 
182 package:
183 	// returns number of pools frozen
184 	size_t freeze(size_t n = 1) {
185 		auto minsz = min(m_fstack.length, n);
186 
187 		if (minsz > 0) {
188 			auto frozen = m_fstack.freeze(minsz);
189 			m_ffreezer.push(frozen);
190 		}
191 
192 		if (minsz < n) {
193 			auto tsz = min(m_tstack.length, n - minsz);
194 			if (tsz > 0) {
195 				auto frozen = m_tstack.freeze(tsz);
196 			 	m_tfreezer.push(frozen);
197 			}
198 			return tsz + minsz;
199 		}
200 		return minsz;
201 	}
202 
203 	// returns number of pools unfrozen
204 	size_t unfreeze(size_t n = 1) {
205 		auto minsz = min(m_ffreezer.length, n);
206 		
207 		if (minsz > 0) m_fstack.unfreeze(m_ffreezer.pop(minsz));
208 		
209 		if (minsz < n) {
210 			auto tsz = min(m_tfreezer.length, n - minsz);
211 			if (tsz > 0) m_tstack.unfreeze(m_tfreezer.pop(tsz));
212 			return tsz + minsz;
213 		}
214 		return minsz;
215 	}
216 
217 	~this() {
218 		destroy(m_fstack);
219 		destroy(m_tstack);
220 	}
221 
222 private static:
223 	// active
224 	ThreadPoolStack m_tstack;
225 	FiberPoolStack m_fstack;
226 
227 	// frozen
228 	ThreadPoolFreezer m_tfreezer;
229 	FiberPoolFreezer m_ffreezer;
230 
231 }
232 
233 package:
234 
235 alias Pool = PoolAllocator!(AutoFreeListAllocator!(MallocAllocator));
236 alias ManagedPool = RefCounted!(Pool);
237 
238 /// User utility for allocating on lower level pools
239 struct ThreadPoolFreezer 
240 {
241 	@disable this(this);
242 	@property size_t length() const { return m_pools.length; }
243 	@property bool empty() const { return length == 0; }
244 
245 	void push(Array!(ManagedPool, Malloc) pools)
246 	{
247 		//logTrace("Push Thread Freezer of ", m_pools.length);
248 		// insert sorted
249 		foreach(ref item; pools[]) {
250 			bool found;
251 			foreach (size_t i, ref el; m_pools[]) {
252 				if (item.id < el.id) {
253 					m_pools.insertBefore(i, item);
254 					found = true;
255 					break;
256 				}
257 			}
258 			if (!found) m_pools ~= item;
259 		}
260 		//logTrace("Pushed Thread Freezer now ", m_pools.length);
261 	}
262 
263 	Array!(ManagedPool, Malloc) pop(size_t n) {
264 		assert(!empty);
265 		//logTrace("Pop Thread Freezer of ", m_pools.length, " id ", m_pools.back.id);
266 		// already sorted
267 		auto pools = Array!(ManagedPool, Malloc)( m_pools[$-n .. $] );
268 
269 		
270 		m_pools.length = (m_pools.length - 1);
271 		//logTrace("Popped Thread Freezer returning ", pools.length, " expecting ", n);
272 		//logTrace("Returning ID ", pools.back.id);
273 		return pools;
274 	}
275 	
276 package:
277 	Vector!(ManagedPool, Malloc) m_pools;
278 }
279 
280 /// User utility for allocating on lower level pools
281 struct FiberPoolFreezer
282 {
283 	@disable this(this);
284 	@property size_t fibers() const { return m_pools.length; }
285 	
286 	@property size_t length() const { 
287 		Fiber f = Fiber.getThis();
288 		if (auto ptr = (f in m_pools)) {
289 			return (*ptr).length;
290 		}
291 		return 0;
292 	}
293 
294 	@property bool empty() const {
295 		return length == 0; 
296 	}
297 
298 	void push(Array!(ManagedPool, Malloc) pools)
299 	{
300 		//logTrace("Push Fiber Freezer of ", length);
301 		Fiber f = Fiber.getThis();
302 		assert(f !is null);
303 		if (auto ptr = (f in m_pools)) {
304 			auto arr = *ptr;
305 
306 			// insert sorted
307 			foreach(ref item; pools[]) {
308 				bool found;
309 				foreach (size_t i, ref el; arr[]) {
310 					if (item.id < el.id) {
311 						arr.insertBefore(i, item);
312 						found = true;
313 						break;
314 					}
315 				}
316 				if (!found) arr ~= item;
317 			}
318 			//logTrace("Pushed Fiber Freezer of ", length);
319 			return;
320 		}
321 		//else
322 		m_pools[f] = pools.dupr;
323 		//logTrace("Pushed Fiber Freezer of ", length);
324 	}
325 
326 	Array!(ManagedPool, Malloc) pop(size_t n) {
327 		//logTrace("Pop Fiber Freezer of ", length);
328 		assert(!empty);
329 		
330 		Fiber f = Fiber.getThis();
331 		auto arr = m_pools[f];
332 
333 		if (arr.empty) {
334 			m_pools.remove(f);
335 			return Array!(ManagedPool, Malloc)();
336 		}
337 
338 		// already sorted
339 		auto pools = Array!(ManagedPool, Malloc)( arr[$-n .. $] );
340 		arr.length = (arr.length - n);
341 		//logTrace("Popped Fiber Freezer of ", length);
342 		return pools;
343 	}
344 
345 private:
346 
347 	HashMap!(Fiber, Array!(ManagedPool, Malloc), Malloc) m_pools;
348 }
349 struct ThreadPoolStack
350 {
351 	@disable this(this);
352 	@property size_t length() const { return m_pools.length; }
353 	@property bool empty() const { return length == 0; }
354 	size_t opDollar() const { return length; }
355 	@property bool hasTop() { return length > 0 && cnt-1 == top.id; }
356 
357 
358 	ManagedPool opIndex(size_t n) {
359 		//logTrace("OpIndex[", n, "] in Thread Pool of ", length, " top: ", cnt, " id: ", m_pools[n].id);
360 		return m_pools[n];
361 	}
362 
363 	@property ManagedPool top() 
364 	{
365 		//logTrace("Front Thread Pool of ", length);
366 		if (empty) {
367 			//logTrace("Empty");
368 			return ManagedPool();
369 		}
370 		return m_pools.back;
371 	}
372 
373 	void pop()
374 	{
375 		assert(!empty);
376 		//logTrace("Pop Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
377 		auto pool = m_pools.back;
378 		assert(pool.id == cnt-1);
379 		--cnt;
380 		m_pools.removeBack();
381 		//if (!empty) logTrace("Popped Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
382 	}
383 
384 	void push() {
385 		//if (!m_pools.empty) logTrace("Push Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
386 		//else logTrace("Push Thread Pool of ", length, " top: ", cnt);
387 		ManagedPool pool = ManagedPool();
388 		pool.id = cnt++;
389 		m_pools.pushBack(pool);
390 		//logTrace("Pushed Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
391 	}
392 
393 	Array!(ManagedPool, Malloc) freeze(size_t n) {
394 		assert(!empty);
395 		//if (!m_pools.empty) logTrace("Freeze ", n, " in Thread Pool of ", length, " top: ", cnt);
396 		//else logTrace("Freeze ", n, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
397 		assert(n <= length);
398 		Array!(ManagedPool, Malloc) ret;
399 		ret[] = m_pools[$-n .. $];
400 		m_pools.length = (m_pools.length - 1);
401 		//logTrace("Returning ", ret.length);
402 		//if (!empty) logTrace("Freezeed ", n, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
403 		return ret;
404 	}
405 
406 	void unfreeze(Array!(ManagedPool, Malloc) pools) {
407 		//logTrace("Unfreeze ", pools.length, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
408 		// insert sorted
409 		foreach(ref item; pools[]) {
410 			bool found;
411 			foreach (size_t i, ref el; m_pools[]) {
412 				if (item.id < el.id) {
413 					m_pools.insertBefore(i, item);
414 					found = true;
415 					break;
416 				}
417 			}
418 			if (!found) m_pools ~= item;
419 		}
420 		//logTrace("Unfreezed ", pools.length, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
421 	}
422 
423 package:
424 	int cnt;
425 	Vector!(ManagedPool, Malloc) m_pools;
426 }
427 
428 struct FiberPoolStack
429 {
430 	@disable this(this);
431 	@property size_t fibers() const { return m_pools.length; }
432 
433 	@property size_t length() const {
434 		Fiber f = Fiber.getThis();
435 		if (auto ptr = (f in m_pools)) {
436 			return (*ptr).length;
437 		}
438 		return 0;
439 	}
440 
441 	@property bool hasTop() { return length > 0 && cnt-1 == top.id; }
442 
443 	@property bool empty() const {
444 		return length == 0; 
445 	}
446 
447 	size_t opDollar() const { return length; }
448 
449 	ManagedPool opIndex(size_t n) {
450 		assert(!empty);
451 		Fiber f = Fiber.getThis();
452 		//logTrace("OpIndex[", n, "] in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f][n].id);
453 		return m_pools[f][n];
454 
455 	}
456 
457 	@property ManagedPool top() 
458 	{
459 		assert(!empty);
460 		Fiber f = Fiber.getThis();
461 		if (auto ptr = (f in m_pools)) {
462 			//logTrace("top in Fiber Pool of ", length, " top: ", cnt, " len: ", (*ptr).back().id);
463 			return (*ptr).back();
464 		}
465 		return ManagedPool();
466 
467 	}
468 
469 	// returns next item ie. top()
470 	void pop() {
471 		assert(!empty);
472 
473 		Fiber f = Fiber.getThis();
474 		//logTrace("pop in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
475 		auto arr = m_pools[f];
476 		assert(arr.back.id == cnt-1);
477 		arr.removeBack();
478 		cnt++;
479 		if (arr.empty) 
480 			m_pools.remove(f);
481 		//if (!empty) logTrace("popped in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
482 	}
483 
484 	void push()
485 	{
486 		ManagedPool pool = ManagedPool();
487 		pool.id = cnt++;
488 		Fiber f = Fiber.getThis();
489 		//if (!empty) logTrace("Push in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools.get(f).back.id);
490 		assert(f !is null);
491 		if (auto ptr = (f in m_pools)) {
492 			*ptr ~= pool;
493 			//logTrace("Pushed in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
494 			return;
495 		}
496 		//else
497 		m_pools[f] = Array!(ManagedPool, Malloc)();
498 		m_pools[f] ~= pool;
499 		//logDebug("Pushed in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
500 	}
501 
502 	// returns the frozen items
503 	Array!(ManagedPool, Malloc) freeze(size_t n) {
504 		assert(n <= length);
505 		Fiber f = Fiber.getThis();
506 		//logTrace("Freeze in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
507 		auto arr = m_pools[f];
508 		Array!(ManagedPool, Malloc) ret;
509 		ret[] = arr[$-n .. $];
510 		arr.length = (arr.length - n);
511 		//logTrace("Frozen in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
512 		return ret;
513 	}
514 
515 
516 	void unfreeze(Array!(ManagedPool, Malloc) items)
517 	{
518 		Fiber f = Fiber.getThis();
519 		assert(f !is null);
520 		//logTrace("Unfreeze in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
521 		if (auto ptr = (f in m_pools)) {
522 			auto arr = *ptr;
523 			// insert sorted
524 			foreach(ref item; items[]) {
525 				bool found;
526 				foreach (size_t i, ref el; arr[]) {
527 					if (item.id < el.id) {
528 						arr.insertBefore(i, item);
529 						found = true;
530 						break;
531 					}
532 				}
533 				if (!found) arr ~= item;
534 			}
535 			//logTrace("Unfrozen in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
536 			return;
537 		}
538 		assert(false);
539 	}
540 package:
541 	int cnt;
542 	HashMap!(Fiber, Array!(ManagedPool, Malloc), Malloc) m_pools;
543 }
544 
545 
546 private void registerPoolArray(T)(ref T arr) {
547 	import std.range : ElementType;
548 	// Add destructors to fiber pool
549 	static if (is(T == struct) && (hasElaborateDestructor!(ElementType!T) || __traits(hasMember, ElementType!T, "__dtor") )) {
550 		foreach (ref el; arr)
551 			PoolStack.top.onDestroy(&el.__dtor);
552 	}
553 }
554 
555 private void reregisterPoolArray(T)(ref T arr, ref T arr2) {
556 	import std.range : ElementType;
557 	// Add destructors to fiber pool
558 	static if (is(T == struct) && (hasElaborateDestructor!(ElementType!T) || __traits(hasMember, ElementType!T, "__dtor") )) {
559 		if (arr.ptr is arr2.ptr && arr2.length > arr.length) {
560 			foreach (ref el; arr2[arr.length - 1 .. $])
561 				PoolStack.top.onDestroy(&el.__dtor);
562 		}
563 		else {
564 			PoolStack.top.removeArrayDtors(&arr.back.__dtor, arr.length);
565 			registerPoolArray(arr2);
566 		}
567 	}
568 }