@@ -705,6 +705,76 @@ final class Tests: XCTestCase {
705
705
lifecycle. wait ( )
706
706
}
707
707
708
+ // this is an example of how state can be managed inside a `LifecycleItem`
709
+ // note the use of locks in this example since there could be concurrent access issues
710
+ // in case shutdown is called (e.g. via signal trap) during the startup sequence
711
+ // see also `testExternalState` test case
712
+ func testInternalState( ) {
713
+ class Item {
714
+ enum State : Equatable {
715
+ case idle
716
+ case starting
717
+ case started( String )
718
+ case shuttingDown
719
+ case shutdown
720
+ }
721
+
722
+ var state = State . idle
723
+ let stateLock = Lock ( )
724
+
725
+ let queue = DispatchQueue ( label: " test " )
726
+
727
+ let data : String
728
+
729
+ init ( _ data: String ) {
730
+ self . data = data
731
+ }
732
+
733
+ func start( callback: @escaping ( Error ? ) -> Void ) {
734
+ self . stateLock. withLock {
735
+ self . state = . starting
736
+ }
737
+ self . queue. asyncAfter ( deadline: . now( ) + Double. random ( in: 0.01 ... 0.1 ) ) {
738
+ self . stateLock. withLock {
739
+ self . state = . started( self . data)
740
+ }
741
+ callback ( nil )
742
+ }
743
+ }
744
+
745
+ func shutdown( callback: @escaping ( Error ? ) -> Void ) {
746
+ self . stateLock. withLock {
747
+ self . state = . shuttingDown
748
+ }
749
+ self . queue. asyncAfter ( deadline: . now( ) + Double. random ( in: 0.01 ... 0.1 ) ) {
750
+ self . stateLock. withLock {
751
+ self . state = . shutdown
752
+ }
753
+ callback ( nil )
754
+ }
755
+ }
756
+ }
757
+
758
+ let expectedData = UUID ( ) . uuidString
759
+ let item = Item ( expectedData)
760
+ let lifecycle = Lifecycle ( )
761
+ lifecycle. register ( label: " test " ,
762
+ start: . async( item. start) ,
763
+ shutdown: . async( item. shutdown) )
764
+
765
+ lifecycle. start ( configuration: . init( shutdownSignal: nil ) ) { error in
766
+ XCTAssertNil ( error, " not expecting error " )
767
+ XCTAssertEqual ( item. state, . started( expectedData) , " expected item to be shutdown, but \( item. state) " )
768
+ lifecycle. shutdown ( )
769
+ }
770
+ lifecycle. wait ( )
771
+ XCTAssertEqual ( item. state, . shutdown, " expected item to be shutdown, but \( item. state) " )
772
+ }
773
+
774
+ // this is an example of how state can be managed outside the `Lifecycle`
775
+ // note the use of locks in this example since there could be concurrent access issues
776
+ // in case shutdown is called (e.g. via signal trap) during the startup sequence
777
+ // see also `testInternalState` test case, which is the prefered way to manage item's state
708
778
func testExternalState( ) {
709
779
enum State : Equatable {
710
780
case idle
@@ -713,36 +783,48 @@ final class Tests: XCTestCase {
713
783
}
714
784
715
785
class Item {
716
- let eventLoopGroup : EventLoopGroup = MultiThreadedEventLoopGroup ( numberOfThreads : 1 )
786
+ let queue = DispatchQueue ( label : " test " )
717
787
718
788
let data : String
789
+
719
790
init ( _ data: String ) {
720
791
self . data = data
721
792
}
722
793
723
- func start( ) -> EventLoopFuture < String > {
724
- return self . eventLoopGroup. next ( ) . makeSucceededFuture ( self . data)
794
+ func start( callback: @escaping ( String ) -> Void ) {
795
+ self . queue. asyncAfter ( deadline: . now( ) + Double. random ( in: 0.01 ... 0.1 ) ) {
796
+ callback ( self . data)
797
+ }
725
798
}
726
799
727
- func shutdown( ) -> EventLoopFuture < Void > {
728
- return self . eventLoopGroup. next ( ) . makeSucceededFuture ( ( ) )
800
+ func shutdown( callback: @escaping ( ) -> Void ) {
801
+ self . queue. asyncAfter ( deadline: . now( ) + Double. random ( in: 0.01 ... 0.1 ) ) {
802
+ callback ( )
803
+ }
729
804
}
730
805
}
731
806
732
807
var state = State . idle
808
+ let stateLock = Lock ( )
733
809
734
810
let expectedData = UUID ( ) . uuidString
735
811
let item = Item ( expectedData)
736
812
let lifecycle = Lifecycle ( )
737
813
lifecycle. register ( label: " test " ,
738
- start: . eventLoopFuture {
739
- item. start ( ) . map { data -> Void in
740
- state = . started( data)
814
+ start: . async { callback in
815
+ item. start { data in
816
+ stateLock. withLock {
817
+ state = . started( data)
818
+ }
819
+ callback ( nil )
741
820
}
742
821
} ,
743
- shutdown: . eventLoopFuture {
744
- item. shutdown ( ) . map { _ -> Void in
745
- state = . shutdown
822
+ shutdown: . async { callback in
823
+ item. shutdown {
824
+ stateLock. withLock {
825
+ state = . shutdown
826
+ }
827
+ callback ( nil )
746
828
}
747
829
} )
748
830
0 commit comments