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 }