1 /*******************************************************************************
2 3 Helper structs for a specific execution context to acquire and relinquish
4 resources from a shared pool.
5 6 (Examples of an "execution context" include `Task`s or connection handlers.)
7 8 Several utilities are provided in this module, all of which build on top of
9 `FreeList`:
10 * `Acquired`: Tracks instances of a specific type acquired from the
11 shared resources and automatically relinquishes them when the
12 execution context exits.
13 * `AcquiredArraysOf`: Tracks arrays of a specific type acquired from the
14 shared resources and automatically relinquishes them when the
15 execution context exits.
16 * `AcquiredSingleton`: Tracks a singleton instance acquired from the
17 shared resources and automatically relinquishes it when the execution
18 context exits.
19 20 The normal approach to using these utilities is as follows:
21 1. Create a class to store the shared resources pools. Let's call it
22 `SharedResources`.
23 2. Add your resource pools to `SharedResources`. A `FreeList!(ubyte[])`
24 is required, but you should add pools of other types you need as
25 well.
26 3. New the resource pools in the constructor.
27 4. Create a nested class inside `SharedResources`. An instance of this
28 class will be newed at scope inside each execution context, and will
29 track the shared resources that the execution context has acquired.
30 Let's call it `AcquiredResources`.
31 5. Add `Acquired*` private members to `AcquiredResources`, as required.
32 There should be one member per type of resource that the execution
33 context might need to acquire.
34 6. Initialise the acquired members in the constructor, and relinquish
35 them in the destructor.
36 7. Add public getters for each type of resource that can be acquired.
37 These should call the `acquire` method of the appropriate acquired
38 member, and return the newly acquired instance to the user.
39 40 Usage examples:
41 See documented unittests of `Acquired`, `AcquiredArraysOf`, and
42 `AcquiredSingleton`.
43 44 Copyright:
45 Copyright (c) 2016-2018 dunnhumby Germany GmbH.
46 All rights reserved
47 48 License:
49 Boost Software License Version 1.0. See LICENSE.txt for details.
50 51 *******************************************************************************/52 53 moduleocean.util.container.pool.AcquiredResources;
54 55 importocean.meta.types.Qualifiers;
56 importocean.core.Verify;
57 importocean.util.container.pool.FreeList;
58 59 /*******************************************************************************
60 61 Set of resources of the templated type acquired by an execution context. An
62 external source of elements of this type -- a FreeList!(T) -- as well as a
63 source of untyped buffers -- a FreeList!(ubyte[]) -- is required. When
64 resources are acquired (via the acquire() method), they are requested from
65 the free list and stored internally in an array. When the resources are no
66 longer required, the relinquishAll() method will return them to the free
67 list. Note that the array used to store the acquired resources is itself
68 acquired from the free list of untyped buffers and relinquished by
69 relinquishAll().
70 71 Params:
72 T = type of resource
73 74 *******************************************************************************/75 76 publicstructAcquired ( T )
77 {
78 importocean.util.container.VoidBufferAsArrayOf;
79 80 /// Type of a new resource. (Differs for reference / value types.)81 staticif ( is(typeof({T* t = newT;})) )
82 {
83 aliasT* Elem;
84 }
85 else86 {
87 aliasTElem;
88 }
89 90 /// Externally owned pool of untyped buffers, passed in via initialise().91 privateFreeList!(ubyte[]) buffer_pool;
92 93 /// Externally owned pool of T, passed in via initialise().94 privateFreeList!(T) t_pool;
95 96 /// List of acquired resources.97 privateVoidBufferAsArrayOf!(Elem) acquired;
98 99 /// Backing buffer for this.acquired.100 privatevoid[] buffer;
101 102 /***************************************************************************
103 104 Initialises this instance. (No other methods may be called before
105 calling this method.)
106 107 Params:
108 buffer_pool = shared pool of untyped arrays
109 t_pool = shared pool of T
110 111 ***************************************************************************/112 113 publicvoidinitialise ( FreeList!(ubyte[]) buffer_pool, FreeList!(T) t_pool )
114 {
115 this.buffer_pool = buffer_pool;
116 this.t_pool = t_pool;
117 }
118 119 /***************************************************************************
120 121 Gets a new T.
122 123 Params:
124 new_t = lazily initialised new resource
125 126 Returns:
127 a new T
128 129 ***************************************************************************/130 131 publicElemacquire ( lazyElemnew_t )
132 {
133 verify(this.buffer_pool !isnull);
134 135 // Acquire container buffer, if not already done.136 if ( this.bufferisnull )
137 {
138 this.buffer = acquireBuffer(this.buffer_pool, Elem.sizeof * 4);
139 this.acquired = VoidBufferAsArrayOf!(Elem)(&this.buffer);
140 }
141 142 // Acquire new element.143 this.acquired ~= this.t_pool.get(new_t);
144 145 returnthis.acquired.array()[$-1];
146 }
147 148 /***************************************************************************
149 150 Relinquishes all shared resources acquired by this instance.
151 152 ***************************************************************************/153 154 publicvoidrelinquishAll ( )
155 {
156 verify(this.buffer_pool !isnull);
157 158 if ( this.buffer !isnull )
159 {
160 // Relinquish acquired Ts.161 foreach ( refinst; this.acquired.array() )
162 this.t_pool.recycle(inst);
163 164 // Relinquish container buffer.165 this.buffer_pool.recycle(cast(ubyte[])this.buffer);
166 }
167 }
168 }
169 170 ///171 unittest172 {
173 // Type of a specialised resource which may be required by an execution174 // context.175 structMyResource176 {
177 }
178 179 // Demonstrates how a typical global shared resources container should look.180 // A single instance of this would be owned at the top level of the app.181 classSharedResources182 {
183 importocean.util.container.pool.FreeList;
184 185 // The pool of untyped buffers required by Acquired.186 privateFreeList!(ubyte[]) buffers;
187 188 // The pool of specialised resources required by Acquired.189 privateFreeList!(MyResource) myresources;
190 191 this ( )
192 {
193 this.buffers = newFreeList!(ubyte[]);
194 this.myresources = newFreeList!(MyResource);
195 }
196 197 // Objects of this class will be newed at scope and passed to execution198 // contexts. This allows the context to acquire various shared resources199 // and have them automatically relinquished when it exits.200 classContextResources201 {
202 // Tracker of resources acquired by the context.203 privateAcquired!(MyResource) acquired_myresources;
204 205 // Initialise the tracker in the ctor.206 this ( )
207 {
208 this.acquired_myresources.initialise(this.outer.buffers,
209 this.outer.myresources);
210 }
211 212 // ...and be sure to relinquish all the acquired resources in the213 // dtor.214 ~this ( )
215 {
216 this.acquired_myresources.relinquishAll();
217 }
218 219 // Public method to get a new resource, managed by the tracker.220 publicMyResource* getMyResource ( )
221 {
222 returnthis.acquired_myresources.acquire(newMyResource);
223 }
224 }
225 }
226 227 // Demonstrates the usage of the shared resources and the context resources.228 classContext229 {
230 SharedResourcesresources;
231 232 voidentryPoint ( )
233 {
234 // New a ContextResources as scope, so that its dtor will be called235 // at scope exit and all acquired resources relinquished.236 scopeacquired = this.resources..newContextResources;
237 238 // Acquire some resources.239 autor1 = acquired.getMyResource();
240 autor2 = acquired.getMyResource();
241 autor3 = acquired.getMyResource();
242 }
243 }
244 }
245 246 /*******************************************************************************
247 248 Set of acquired arrays of the templated type acquired by an execution
249 context. An external source of untyped arrays -- a FreeList!(ubyte[]) -- is
250 required. When arrays are acquired (via the acquire() method), they are
251 requested from the free list and stored internally in a container array.
252 When the arrays are no longer required, the relinquishAll() method will
253 return them to the free list. Note that the container array used to store
254 the acquired arrays is also itself acquired from the free list and
255 relinquished by relinquishAll().
256 257 Params:
258 T = element type of the arrays
259 260 *******************************************************************************/261 262 publicstructAcquiredArraysOf ( T )
263 {
264 importocean.util.container.VoidBufferAsArrayOf;
265 266 /// Externally owned pool of untyped buffers, passed in via initialise().267 privateFreeList!(ubyte[]) buffer_pool;
268 269 /// List of void[] backing buffers for acquired arrays of T. This array is270 /// stored as a VoidBufferAsArrayOf!(void[]) in order to be able to handle271 /// it as if it's a void[][], where it's actually a simple void[] under the272 /// hood.273 privateVoidBufferAsArrayOf!(void[]) acquired;
274 275 /// Backing buffer for this.acquired.276 privatevoid[] buffer;
277 278 /***************************************************************************
279 280 Initialises this instance. (No other methods may be called before
281 calling this method.)
282 283 Params:
284 buffer_pool = shared pool of untyped arrays
285 286 ***************************************************************************/287 288 publicvoidinitialise ( FreeList!(ubyte[]) buffer_pool )
289 {
290 this.buffer_pool = buffer_pool;
291 }
292 293 /***************************************************************************
294 295 Figure out the return type of this.acquire. It's pointless (and not
296 possible) to have a VoidBufferAsArrayOf!(void), so if T is void, we only
297 need a method to return a void[]* directly. If T is not void, we need a
298 method to return a VoidBufferAsArrayOf!(T).
299 300 ***************************************************************************/301 302 staticif (is(T == void) )
303 {
304 /***********************************************************************
305 306 Gets a pointer to a new array, acquired from the shared resources
307 pool.
308 309 Returns:
310 pointer to a new void[]
311 312 ***********************************************************************/313 314 publicvoid[]* acquire ( )
315 {
316 returnthis.acquireNewBuffer();
317 }
318 }
319 else320 {
321 /***********************************************************************
322 323 Gets a new void[] wrapped with an API allowing it to be used as a
324 T[], acquired from the shared resources pool.
325 326 Returns:
327 a void[] wrapped with an API allowing it to be used as a T[]
328 329 ***********************************************************************/330 331 publicVoidBufferAsArrayOf!(T) acquire ( )
332 {
333 autonew_buf = this.acquireNewBuffer();
334 returnVoidBufferAsArrayOf!(T)(new_buf);
335 }
336 }
337 338 /***************************************************************************
339 340 Relinquishes all shared resources acquired by this instance.
341 342 ***************************************************************************/343 344 publicvoidrelinquishAll ( )
345 {
346 verify(this.buffer_pool !isnull);
347 348 if ( this.buffer !isnull )
349 {
350 // Relinquish acquired buffers.351 foreach ( refinst; this.acquired.array() )
352 this.buffer_pool.recycle(cast(ubyte[])inst);
353 354 // Relinquish container buffer.355 this.buffer_pool.recycle(cast(ubyte[])this.buffer);
356 }
357 }
358 359 /***************************************************************************
360 361 Gets a void[] from the pool of buffers, appends it to the list of
362 acquired buffers, then returns a pointer to element in the list.
363 364 Returns:
365 a pointer to a new void[] in the list of acquired buffers
366 367 ***************************************************************************/368 369 privatevoid[]* acquireNewBuffer ( )
370 {
371 verify(this.buffer_pool !isnull);
372 373 enuminitial_array_capacity = 4;
374 375 // Acquire container buffer, if not already done.376 if ( this.bufferisnull )
377 {
378 this.buffer = acquireBuffer(this.buffer_pool,
379 (void[]).sizeof * initial_array_capacity);
380 this.acquired = VoidBufferAsArrayOf!(void[])(&this.buffer);
381 }
382 383 // Acquire and re-initialise new buffer to return to the user. Store384 // it in the container buffer.385 this.acquired ~= acquireBuffer(this.buffer_pool,
386 T.sizeof * initial_array_capacity);
387 388 return &(this.acquired.array()[$-1]);
389 }
390 }
391 392 ///393 unittest394 {
395 // Demonstrates how a typical global shared resources container should look.396 // A single instance of this would be owned at the top level of the app.397 classSharedResources398 {
399 importocean.util.container.pool.FreeList;
400 401 // The pool of untyped buffers required by AcquiredArraysOf.402 privateFreeList!(ubyte[]) buffers;
403 404 this ( )
405 {
406 this.buffers = newFreeList!(ubyte[]);
407 }
408 409 // Objects of this class will be newed at scope and passed to execution410 // contexts. This allows the context to acquire various shared resources411 // and have them automatically relinquished when it exits.412 classContextResources413 {
414 // Tracker of buffers acquired by the context.415 privateAcquiredArraysOf!(void) acquired_void_buffers;
416 417 // Initialise the tracker in the ctor.418 this ( )
419 {
420 this.acquired_void_buffers.initialise(this.outer.buffers);
421 }
422 423 // ...and be sure to relinquish all the acquired resources in the424 // dtor.425 ~this ( )
426 {
427 this.acquired_void_buffers.relinquishAll();
428 }
429 430 // Public method to get a new resource, managed by the tracker.431 publicvoid[]* getVoidBuffer ( )
432 {
433 returnthis.acquired_void_buffers.acquire();
434 }
435 }
436 }
437 438 // Demonstrates the usage of the shared resources and the context resources.439 classContext440 {
441 SharedResourcesresources;
442 443 voidentryPoint ( )
444 {
445 // New a ContextResources as scope, so that its dtor will be called446 // at scope exit and all acquired resources relinquished.447 scopeacquired = this.resources..newContextResources;
448 449 // Acquire some buffers.450 autobuf1 = acquired.getVoidBuffer();
451 autobuf2 = acquired.getVoidBuffer();
452 autobuf3 = acquired.getVoidBuffer();
453 }
454 }
455 }
456 457 /*******************************************************************************
458 459 Singleton (per-execution context) acquired resource of the templated type.
460 An external source of elements of this type -- a FreeList!(T) -- is
461 required. When the singleton resource is acquired (via the acquire()
462 method), it is requested from the free list and stored internally. All
463 subsequent calls to acquire() return the same instance. When the resource is
464 no longer required, the relinquish() method will return it to the free list.
465 466 Params:
467 T = type of resource
468 469 *******************************************************************************/470 471 publicstructAcquiredSingleton ( T )
472 {
473 importocean.util.container.pool.FreeList;
474 475 /// Type of a new resource. (Differs for reference / value types.)476 staticif ( is(typeof({T* t = newT;})) )
477 {
478 aliasT* Elem;
479 }
480 else481 {
482 aliasTElem;
483 }
484 485 /// Externally owned pool of T, passed in via initialise().486 privateFreeList!(T) t_pool;
487 488 /// Acquired resource.489 privateElemacquired;
490 491 /***************************************************************************
492 493 Initialises this instance. (No other methods may be called before
494 calling this method.)
495 496 Params:
497 t_pool = shared pool of T
498 499 ***************************************************************************/500 501 publicvoidinitialise ( FreeList!(T) t_pool )
502 {
503 this.t_pool = t_pool;
504 }
505 506 /***************************************************************************
507 508 Gets the singleton T instance.
509 510 Params:
511 new_t = lazily initialised new resource
512 513 Returns:
514 singleton T instance
515 516 ***************************************************************************/517 518 publicElemacquire ( lazyElemnew_t )
519 {
520 verify(this.t_pool !isnull);
521 522 if ( this.acquiredisnull )
523 this.acquired = this.t_pool.get(new_t);
524 525 verify(this.acquired !isnull);
526 527 returnthis.acquired;
528 }
529 530 /***************************************************************************
531 532 Gets the singleton T instance.
533 534 Params:
535 new_t = lazily initialised new resource
536 reset = delegate to call on the singleton instance when it is first
537 acquired by this execution context from the pool. Should perform
538 any logic required to reset the instance to its initial state
539 540 Returns:
541 singleton T instance
542 543 ***************************************************************************/544 545 publicElemacquire ( lazyElemnew_t, scopevoiddelegate ( Elem ) reset )
546 {
547 verify(this.t_pool !isnull);
548 549 if ( this.acquiredisnull )
550 {
551 this.acquired = this.t_pool.get(new_t);
552 reset(this.acquired);
553 }
554 555 verify(this.acquired !isnull);
556 557 returnthis.acquired;
558 }
559 560 /***************************************************************************
561 562 Relinquishes singleton shared resources acquired by this instance.
563 564 ***************************************************************************/565 566 publicvoidrelinquish ( )
567 {
568 verify(this.t_pool !isnull);
569 570 if ( this.acquired !isnull )
571 this.t_pool.recycle(this.acquired);
572 }
573 }
574 575 ///576 unittest577 {
578 // Type of a specialised resource which may be required by an execution579 // context.580 structMyResource581 {
582 }
583 584 // Demonstrates how a typical global shared resources container should look.585 // A single instance of this would be owned at the top level of the app.586 classSharedResources587 {
588 importocean.util.container.pool.FreeList;
589 590 // The pool of specialised resources required by AcquiredSingleton.591 privateFreeList!(MyResource) myresources;
592 593 this ( )
594 {
595 this.myresources = newFreeList!(MyResource);
596 }
597 598 // Objects of this class will be newed at scope and passed to execution599 // contexts. This allows the context to acquire various shared resources600 // and have them automatically relinquished when it exits.601 classContextResources602 {
603 // Tracker of the singleton resource acquired by the context.604 privateAcquiredSingleton!(MyResource) myresource_singleton;
605 606 // Initialise the tracker in the ctor.607 this ( )
608 {
609 this.myresource_singleton.initialise(this.outer.myresources);
610 }
611 612 // ...and be sure to relinquish all the acquired resources in the613 // dtor.614 ~this ( )
615 {
616 this.myresource_singleton.relinquish();
617 }
618 619 // Public method to get the resource singleton for this execution620 // context, managed by the tracker.621 publicMyResource* myResource ( )
622 {
623 returnthis.myresource_singleton.acquire(newMyResource,
624 ( MyResource* resource )
625 {
626 // When the singleton is first acquired, perform any627 // logic required to reset it to its initial state.628 *resource = MyResource.init;
629 }
630 );
631 }
632 }
633 }
634 635 // Demonstrates the usage of the shared resources and the context resources.636 classContext637 {
638 SharedResourcesresources;
639 640 voidentryPoint ( )
641 {
642 // New a ContextResources as scope, so that its dtor will be called643 // at scope exit and all acquired resources relinquished.644 scopeacquired = this.resources..newContextResources;
645 646 // Acquire a resource.647 acquired.myResource();
648 649 // Acquire the same resource again.650 acquired.myResource();
651 }
652 }
653 }
654 655 /*******************************************************************************
656 657 Helper function used by the structs in this module to acquire a void[]
658 buffer from the specified free list.
659 660 Params:
661 buffer_pool = free list of void[]s to reuse, if one is available
662 capacity = if a new buffer is allocated (i.e. the free list is empty),
663 this argument specifies its initial dimension (in bytes)
664 665 Returns:
666 a buffer acquired from the free list or a newly allocated buffer
667 668 *******************************************************************************/669 670 privatevoid[] acquireBuffer ( FreeList!(ubyte[]) buffer_pool, size_tcapacity )
671 {
672 autobuffer = buffer_pool.get(cast(ubyte[])newvoid[capacity]);
673 buffer.length = 0;
674 assumeSafeAppend(buffer);
675 676 returnbuffer;
677 }
678 679 /*******************************************************************************
680 681 Test that shared resources are acquired and relinquished correctly using the
682 helper structs above.
683 684 *******************************************************************************/685 686 version (unittest)
687 {
688 importocean.core.Test;
689 }
690 691 unittest692 {
693 // Resource types that may be acquired.694 structMyStruct { }
695 classMyClass { }
696 697 classSharedResources698 {
699 importocean.util.container.pool.FreeList;
700 701 privateFreeList!(MyStruct) mystructs;
702 privateFreeList!(MyClass) myclasses;
703 privateFreeList!(ubyte[]) buffers;
704 705 this ( )
706 {
707 this.mystructs = newFreeList!(MyStruct);
708 this.myclasses = newFreeList!(MyClass);
709 this.buffers = newFreeList!(ubyte[]);
710 }
711 712 classContextResources713 {
714 privateAcquired!(MyStruct) acquired_mystructs;
715 privateAcquiredSingleton!(MyStruct) mystruct_singleton;
716 privateAcquired!(MyClass) acquired_myclasses;
717 privateAcquiredArraysOf!(void) acquired_void_arrays;
718 719 this ( )
720 {
721 this.acquired_mystructs.initialise(this.outer.buffers,
722 this.outer.mystructs);
723 this.mystruct_singleton.initialise(this.outer.mystructs);
724 this.acquired_myclasses.initialise(this.outer.buffers,
725 this.outer.myclasses);
726 this.acquired_void_arrays.initialise(this.outer.buffers);
727 }
728 729 ~this ( )
730 {
731 this.acquired_mystructs.relinquishAll();
732 this.mystruct_singleton.relinquish();
733 this.acquired_myclasses.relinquishAll();
734 this.acquired_void_arrays.relinquishAll();
735 }
736 737 publicMyStruct* getMyStruct ( )
738 {
739 returnthis.acquired_mystructs.acquire(newMyStruct);
740 }
741 742 publicMyStruct* myStructSingleton ( )
743 {
744 returnthis.mystruct_singleton.acquire(newMyStruct);
745 }
746 747 publicMyClassgetMyClass ( )
748 {
749 returnthis.acquired_myclasses.acquire(newMyClass);
750 }
751 752 publicvoid[]* getVoidArray ( )
753 {
754 returnthis.acquired_void_arrays.acquire();
755 }
756 }
757 }
758 759 autoresources = newSharedResources;
760 761 // Test acquiring some resources.762 {
763 scopeacquired = resources..newContextResources;
764 test!("==")(resources.buffers.num_idle, 0);
765 test!("==")(resources.mystructs.num_idle, 0);
766 test!("==")(resources.myclasses.num_idle, 0);
767 768 // Acquire a struct.769 acquired.getMyStruct();
770 test!("==")(resources.buffers.num_idle, 0);
771 test!("==")(resources.mystructs.num_idle, 0);
772 test!("==")(resources.myclasses.num_idle, 0);
773 test!("==")(acquired.acquired_mystructs.acquired.length, 1);
774 775 // Acquire a struct singleton twice.776 acquired.myStructSingleton();
777 acquired.myStructSingleton();
778 test!("==")(resources.buffers.num_idle, 0);
779 test!("==")(resources.mystructs.num_idle, 0);
780 test!("==")(resources.myclasses.num_idle, 0);
781 test!("==")(acquired.acquired_mystructs.acquired.length, 1);
782 783 // Acquire a class.784 acquired.getMyClass();
785 test!("==")(resources.buffers.num_idle, 0);
786 test!("==")(resources.mystructs.num_idle, 0);
787 test!("==")(resources.myclasses.num_idle, 0);
788 test!("==")(acquired.acquired_myclasses.acquired.length, 1);
789 790 // Acquire an array.791 acquired.getVoidArray();
792 test!("==")(resources.buffers.num_idle, 0);
793 test!("==")(resources.mystructs.num_idle, 0);
794 test!("==")(resources.myclasses.num_idle, 0);
795 test!("==")(acquired.acquired_void_arrays.acquired.length, 1);
796 }
797 798 // Test that the acquired resources appear in the free-lists, once the799 // acquired tracker goes out of scope.800 test!("==")(resources.buffers.num_idle, 4); // 3 container arrays + 1801 test!("==")(resources.mystructs.num_idle, 2);
802 test!("==")(resources.myclasses.num_idle, 1);
803 804 // Now do it again and test that the resources in the free-lists are reused.805 {
806 scopeacquired = resources..newContextResources;
807 test!("==")(resources.buffers.num_idle, 4);
808 test!("==")(resources.mystructs.num_idle, 2);
809 test!("==")(resources.myclasses.num_idle, 1);
810 811 // Acquire a struct.812 acquired.getMyStruct();
813 test!("==")(resources.buffers.num_idle, 3);
814 test!("==")(resources.mystructs.num_idle, 1);
815 test!("==")(resources.myclasses.num_idle, 1);
816 test!("==")(acquired.acquired_mystructs.acquired.length, 1);
817 818 // Acquire a class.819 acquired.getMyClass();
820 test!("==")(resources.buffers.num_idle, 2);
821 test!("==")(resources.mystructs.num_idle, 1);
822 test!("==")(resources.myclasses.num_idle, 0);
823 test!("==")(acquired.acquired_myclasses.acquired.length, 1);
824 825 // Acquire an array.826 acquired.getVoidArray();
827 test!("==")(resources.buffers.num_idle, 0);
828 test!("==")(resources.mystructs.num_idle, 1);
829 test!("==")(resources.myclasses.num_idle, 0);
830 test!("==")(acquired.acquired_void_arrays.acquired.length, 1);
831 }
832 833 // No more resources should have been allocated.834 test!("==")(resources.buffers.num_idle, 4); // 3 container arrays + 1835 test!("==")(resources.mystructs.num_idle, 2);
836 test!("==")(resources.myclasses.num_idle, 1);
837 }