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 module ocean.util.container.pool.AcquiredResources;
54 
55 import ocean.meta.types.Qualifiers;
56 import ocean.core.Verify;
57 import ocean.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 public struct Acquired ( T )
77 {
78     import ocean.util.container.VoidBufferAsArrayOf;
79 
80     /// Type of a new resource. (Differs for reference / value types.)
81     static if ( is(typeof({T* t = new T;})) )
82     {
83         alias T* Elem;
84     }
85     else
86     {
87         alias T Elem;
88     }
89 
90     /// Externally owned pool of untyped buffers, passed in via initialise().
91     private FreeList!(ubyte[]) buffer_pool;
92 
93     /// Externally owned pool of T, passed in via initialise().
94     private FreeList!(T) t_pool;
95 
96     /// List of acquired resources.
97     private VoidBufferAsArrayOf!(Elem) acquired;
98 
99     /// Backing buffer for this.acquired.
100     private void[] 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     public void initialise ( 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     public Elem acquire ( lazy Elem new_t )
132     {
133         verify(this.buffer_pool !is null);
134 
135         // Acquire container buffer, if not already done.
136         if ( this.buffer is null )
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         return this.acquired.array()[$-1];
146     }
147 
148     /***************************************************************************
149 
150         Relinquishes all shared resources acquired by this instance.
151 
152     ***************************************************************************/
153 
154     public void relinquishAll ( )
155     {
156         verify(this.buffer_pool !is null);
157 
158         if ( this.buffer !is null )
159         {
160             // Relinquish acquired Ts.
161             foreach ( ref inst; 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 unittest
172 {
173     // Type of a specialised resource which may be required by an execution
174     // context.
175     struct MyResource
176     {
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     class SharedResources
182     {
183         import ocean.util.container.pool.FreeList;
184 
185         // The pool of untyped buffers required by Acquired.
186         private FreeList!(ubyte[]) buffers;
187 
188         // The pool of specialised resources required by Acquired.
189         private FreeList!(MyResource) myresources;
190 
191         this ( )
192         {
193             this.buffers = new FreeList!(ubyte[]);
194             this.myresources = new FreeList!(MyResource);
195         }
196 
197         // Objects of this class will be newed at scope and passed to execution
198         // contexts. This allows the context to acquire various shared resources
199         // and have them automatically relinquished when it exits.
200         class ContextResources
201         {
202             // Tracker of resources acquired by the context.
203             private Acquired!(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 the
213             // dtor.
214             ~this ( )
215             {
216                 this.acquired_myresources.relinquishAll();
217             }
218 
219             // Public method to get a new resource, managed by the tracker.
220             public MyResource* getMyResource ( )
221             {
222                 return this.acquired_myresources.acquire(new MyResource);
223             }
224         }
225     }
226 
227     // Demonstrates the usage of the shared resources and the context resources.
228     class Context
229     {
230         SharedResources resources;
231 
232         void entryPoint ( )
233         {
234             // New a ContextResources as scope, so that its dtor will be called
235             // at scope exit and all acquired resources relinquished.
236             scope acquired = this.resources..new ContextResources;
237 
238             // Acquire some resources.
239             auto r1 = acquired.getMyResource();
240             auto r2 = acquired.getMyResource();
241             auto r3 = 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 public struct AcquiredArraysOf ( T )
263 {
264     import ocean.util.container.VoidBufferAsArrayOf;
265 
266     /// Externally owned pool of untyped buffers, passed in via initialise().
267     private FreeList!(ubyte[]) buffer_pool;
268 
269     /// List of void[] backing buffers for acquired arrays of T. This array is
270     /// stored as a VoidBufferAsArrayOf!(void[]) in order to be able to handle
271     /// it as if it's a void[][], where it's actually a simple void[] under the
272     /// hood.
273     private VoidBufferAsArrayOf!(void[]) acquired;
274 
275     /// Backing buffer for this.acquired.
276     private void[] 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     public void initialise ( 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     static if (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         public void[]* acquire ( )
315         {
316             return this.acquireNewBuffer();
317         }
318     }
319     else
320     {
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         public VoidBufferAsArrayOf!(T) acquire ( )
332         {
333             auto new_buf = this.acquireNewBuffer();
334             return VoidBufferAsArrayOf!(T)(new_buf);
335         }
336     }
337 
338     /***************************************************************************
339 
340         Relinquishes all shared resources acquired by this instance.
341 
342     ***************************************************************************/
343 
344     public void relinquishAll ( )
345     {
346         verify(this.buffer_pool !is null);
347 
348         if ( this.buffer !is null )
349         {
350             // Relinquish acquired buffers.
351             foreach ( ref inst; 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     private void[]* acquireNewBuffer ( )
370     {
371         verify(this.buffer_pool !is null);
372 
373         enum initial_array_capacity = 4;
374 
375         // Acquire container buffer, if not already done.
376         if ( this.buffer is null )
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. Store
384         // 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 unittest
394 {
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     class SharedResources
398     {
399         import ocean.util.container.pool.FreeList;
400 
401         // The pool of untyped buffers required by AcquiredArraysOf.
402         private FreeList!(ubyte[]) buffers;
403 
404         this ( )
405         {
406             this.buffers = new FreeList!(ubyte[]);
407         }
408 
409         // Objects of this class will be newed at scope and passed to execution
410         // contexts. This allows the context to acquire various shared resources
411         // and have them automatically relinquished when it exits.
412         class ContextResources
413         {
414             // Tracker of buffers acquired by the context.
415             private AcquiredArraysOf!(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 the
424             // 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             public void[]* getVoidBuffer ( )
432             {
433                 return this.acquired_void_buffers.acquire();
434             }
435         }
436     }
437 
438     // Demonstrates the usage of the shared resources and the context resources.
439     class Context
440     {
441         SharedResources resources;
442 
443         void entryPoint ( )
444         {
445             // New a ContextResources as scope, so that its dtor will be called
446             // at scope exit and all acquired resources relinquished.
447             scope acquired = this.resources..new ContextResources;
448 
449             // Acquire some buffers.
450             auto buf1 = acquired.getVoidBuffer();
451             auto buf2 = acquired.getVoidBuffer();
452             auto buf3 = 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 public struct AcquiredSingleton ( T )
472 {
473     import ocean.util.container.pool.FreeList;
474 
475     /// Type of a new resource. (Differs for reference / value types.)
476     static if ( is(typeof({T* t = new T;})) )
477     {
478         alias T* Elem;
479     }
480     else
481     {
482         alias T Elem;
483     }
484 
485     /// Externally owned pool of T, passed in via initialise().
486     private FreeList!(T) t_pool;
487 
488     /// Acquired resource.
489     private Elem acquired;
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     public void initialise ( 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     public Elem acquire ( lazy Elem new_t )
519     {
520         verify(this.t_pool !is null);
521 
522         if ( this.acquired is null )
523             this.acquired = this.t_pool.get(new_t);
524 
525         verify(this.acquired !is null);
526 
527         return this.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     public Elem acquire ( lazy Elem new_t, scope void delegate ( Elem ) reset )
546     {
547         verify(this.t_pool !is null);
548 
549         if ( this.acquired is null )
550         {
551             this.acquired = this.t_pool.get(new_t);
552             reset(this.acquired);
553         }
554 
555         verify(this.acquired !is null);
556 
557         return this.acquired;
558     }
559 
560     /***************************************************************************
561 
562         Relinquishes singleton shared resources acquired by this instance.
563 
564     ***************************************************************************/
565 
566     public void relinquish ( )
567     {
568         verify(this.t_pool !is null);
569 
570         if ( this.acquired !is null )
571             this.t_pool.recycle(this.acquired);
572     }
573 }
574 
575 ///
576 unittest
577 {
578     // Type of a specialised resource which may be required by an execution
579     // context.
580     struct MyResource
581     {
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     class SharedResources
587     {
588         import ocean.util.container.pool.FreeList;
589 
590         // The pool of specialised resources required by AcquiredSingleton.
591         private FreeList!(MyResource) myresources;
592 
593         this ( )
594         {
595             this.myresources = new FreeList!(MyResource);
596         }
597 
598         // Objects of this class will be newed at scope and passed to execution
599         // contexts. This allows the context to acquire various shared resources
600         // and have them automatically relinquished when it exits.
601         class ContextResources
602         {
603             // Tracker of the singleton resource acquired by the context.
604             private AcquiredSingleton!(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 the
613             // dtor.
614             ~this ( )
615             {
616                 this.myresource_singleton.relinquish();
617             }
618 
619             // Public method to get the resource singleton for this execution
620             // context, managed by the tracker.
621             public MyResource* myResource ( )
622             {
623                 return this.myresource_singleton.acquire(new MyResource,
624                     ( MyResource* resource )
625                     {
626                         // When the singleton is first acquired, perform any
627                         // 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     class Context
637     {
638         SharedResources resources;
639 
640         void entryPoint ( )
641         {
642             // New a ContextResources as scope, so that its dtor will be called
643             // at scope exit and all acquired resources relinquished.
644             scope acquired = this.resources..new ContextResources;
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 private void[] acquireBuffer ( FreeList!(ubyte[]) buffer_pool, size_t capacity )
671 {
672     auto buffer = buffer_pool.get(cast(ubyte[])new void[capacity]);
673     buffer.length = 0;
674     assumeSafeAppend(buffer);
675 
676     return buffer;
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     import ocean.core.Test;
689 }
690 
691 unittest
692 {
693     // Resource types that may be acquired.
694     struct MyStruct { }
695     class MyClass { }
696 
697     class SharedResources
698     {
699         import ocean.util.container.pool.FreeList;
700 
701         private FreeList!(MyStruct) mystructs;
702         private FreeList!(MyClass) myclasses;
703         private FreeList!(ubyte[]) buffers;
704 
705         this ( )
706         {
707             this.mystructs = new FreeList!(MyStruct);
708             this.myclasses = new FreeList!(MyClass);
709             this.buffers = new FreeList!(ubyte[]);
710         }
711 
712         class ContextResources
713         {
714             private Acquired!(MyStruct) acquired_mystructs;
715             private AcquiredSingleton!(MyStruct) mystruct_singleton;
716             private Acquired!(MyClass) acquired_myclasses;
717             private AcquiredArraysOf!(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             public MyStruct* getMyStruct ( )
738             {
739                 return this.acquired_mystructs.acquire(new MyStruct);
740             }
741 
742             public MyStruct* myStructSingleton ( )
743             {
744                 return this.mystruct_singleton.acquire(new MyStruct);
745             }
746 
747             public MyClass getMyClass ( )
748             {
749                 return this.acquired_myclasses.acquire(new MyClass);
750             }
751 
752             public void[]* getVoidArray ( )
753             {
754                 return this.acquired_void_arrays.acquire();
755             }
756         }
757     }
758 
759     auto resources = new SharedResources;
760 
761     // Test acquiring some resources.
762     {
763         scope acquired = resources..new ContextResources;
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 the
799     // acquired tracker goes out of scope.
800     test!("==")(resources.buffers.num_idle, 4); // 3 container arrays + 1
801     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         scope acquired = resources..new ContextResources;
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 + 1
835     test!("==")(resources.mystructs.num_idle, 2);
836     test!("==")(resources.myclasses.num_idle, 1);
837 }