Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An equivalent of Go's select statement? #28

Open
ckornher opened this issue Aug 2, 2020 · 5 comments
Open

An equivalent of Go's select statement? #28

ckornher opened this issue Aug 2, 2020 · 5 comments

Comments

@ckornher
Copy link

ckornher commented Aug 2, 2020

Another feature request:
Go has a select statement that facilitates the use of multiple channels simultaneously for timers, out-of-band control, etc.

Are you planning to add something similar?

https://tour.golang.org/concurrency/5

@belozierov
Copy link
Owner

@ckornher I'm not familiar with go lang, but select looks like an actor. You can do something like this:

// Message types for actor
enum CounterMessages {
    case increment, getCounter(CoPromise<Int>)
}

let actor = DispatchQueue.global().actor(of: CounterMessages.self) { receiver in
    var counter = 0
    for message in receiver {
       switch message {
       case .increment:
            counter += 1
        case .getCounter(let promise):
           promise.success(counter)
       }
    }
}

DispatchQueue.concurrentPerform(iterations: 100_000) { _ in
    actor.offer(.increment)
}

let promise = CoPromise<Int>()
promise.whenSuccess { print($0) }
actor.offer(.getCounter(promise))
actor.close()

@FabianTerhorst
Copy link

In golang you can use select to read from multiple channels in the same coroutine.
E.g.

for i := 0; i < n; i++ {
        select {
        case msgFromChannel1 := <-channel1:
            println("received", msgFromChannel1)
        case msgFromChannel2 := <-channel2:
            println("received", msgFromChannel2)
        }
    }

@belozierov
Copy link
Owner

@FabianTerhorst @ckornher Hi, it looks really great in golang. We can create some "select" channel to combine results from several channels. It may look something like this (may still need to think about which channel closes or cancels whom):

extension CoChannel {
	
   func addSubchannel(_ channel: CoChannel) {
      // Cancel subchannel on complete
      whenComplete(channel.cancel)
      // Offer value when receive result
      channel.whenReceive { _ = $0.map(self.offer) }
   }
	
   func addSubchannel<T>(_ channel: CoChannel<T>, transformer: @escaping (T) -> Element) {
      // Cancel subchannel on complete
      whenComplete(channel.cancel)
      // Offer value when receive result
      channel.whenReceive { result in
         guard let value = try? result.get() else { return }
         self.offer(transformer(value))
      }
   }
}

Then the use may look like this:

enum Foo {
   case caseA(Int), caseB(String)
}

let channelA: CoChannel<Int>
let channelB: CoChannel<String>
		
let selectChannel = CoChannel<Foo>()
selectChannel.addSubchannel(channelA, transformer: Foo.caseA)
selectChannel.addSubchannel(channelB, transformer: Foo.caseB)
		
DispatchQueue.global().startCoroutine {
   for foo in selectChannel.makeIterator() {
      switch foo {
      case .caseA(let a):
         print(a)
      case .caseB(let b):
         print(b)
      }
   }
}

@ckornher
Copy link
Author

ckornher commented Aug 9, 2020

We can create some "select" channel to combine results from several channels.

Yes, this would work except that I think that it suffers from the same problem that I ran into when I tried to use the library as-is. I could not avoid a case where the logic captures values from the "subchannels" while an element is being processed.

Golang never buffers elements within a select and it is safe, for example, to fail the processing of an element and stop a "goroutine" without any other "future" elements being taken from channels in the select.

Golang's select is often used to have a measure of "out-of-band" control over a "goroutine" and buffering would defeat this sort of logic.

Golang select applies a pseudo-random fairness algorithm for "subchannels" that have data. There would be advantages to having priority for channels in some situations, but fairness should be an option, at least.

Golang's select has a default case that executes if none of the "subchannels` has elements. It is rarely used, in my experience, and almost never within a loop, so it would be a "nice to have" feature IMO.

I do not see a way around something like a multi-channel await() method. Pushing elements back to the front of a channel could re-order elements when multiple coroutines are reading a channel. This is common in Go's "pipeline" pattern.

@belozierov
Copy link
Owner

@ckornher @FabianTerhorst Thank you for explanation. It seems that select is really important functionality that I've missed. I need some free time to think how to implement it properly.

ladeiko added a commit to ladeiko/SwiftCoroutine that referenced this issue Aug 5, 2022
…detect mods to free blocks

SwiftCoroutineDemo(39856,0x11266e600) malloc: nano zone abandoned due to inability to preallocate reserved vm space.
DispatchQueue::scheduleTask() <OS_dispatch_queue_main: com.apple.main-thread>
SharedCoroutineDispatcher::getFreeQueue(SwiftCoroutine.SharedCoroutineDispatcher)
CoroutineContext::init(stackSize=196608, guardPage=true, SwiftCoroutine.CoroutineContext) returnEnv = 0x000060f0000042d0 stack = 0x000000010effb000
SharedCoroutineQueue::start(SwiftCoroutine.SharedCoroutineQueue)
CoroutineContext::start(SwiftCoroutine.CoroutineContext) enter
CoroutineContext::stackTop(SwiftCoroutine.CoroutineContext) 0x000000010f02b000
CoroutineContext::start(Optional(0x00006060000b8400)) block enter
==39856==WARNING: ASan is ignoring requested __asan_handle_no_return: stack type: default top: 0x7ff7b7045000; bottom 0x00010f028000; size: 0x7ff6a801d000 (140697357373440)
False positive error reports may follow
For details see google/sanitizers#189
CoroutineContext::start(SwiftCoroutine.CoroutineContext) leave
DispatchQueue::scheduleTask() <OS_dispatch_queue_main: com.apple.main-thread>
SharedCoroutineDispatcher::getFreeQueue(SwiftCoroutine.SharedCoroutineDispatcher)
SharedCoroutineQueue::start(SwiftCoroutine.SharedCoroutineQueue)
CoroutineContext::stackTop(SwiftCoroutine.CoroutineContext) 0x000000010f02b000
=================================================================
==39856==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x00010f029ec1 at pc 0x00010949d77d bp 0x7ff7b7040e90 sp 0x7ff7b7040650
READ of size 4416 at 0x00010f029ec1 thread T0
    #0 0x10949d77c in wrap_memcpy+0x16c (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x1977c)
    belozierov#1 0x109ef7f83 in SharedCoroutine.saveStack() SharedCoroutine.swift:79
    belozierov#2 0x109eff514 in SharedCoroutineQueue.start(dispatcher:scheduler:task:) SharedCoroutineQueue.swift:39
    belozierov#3 0x109efc229 in closure belozierov#1 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:27
    belozierov#4 0x109ee7ed9 in OS_dispatch_queue.scheduleTask(_:) CoroutineScheduler+DispatchQueue.swift:16
    belozierov#5 0x109ee88ad in protocol witness for CoroutineScheduler.scheduleTask(_:) in conformance OS_dispatch_queue <compiler-generated>
    belozierov#6 0x109efbf48 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:26
    belozierov#7 0x109ee3616 in CoroutineScheduler._startCoroutine(_:) CoroutineScheduler.swift:51
    belozierov#8 0x109ee3c78 in CoroutineScheduler.startCoroutine(in:task:) CoroutineScheduler.swift:69
    belozierov#9 0x108ebe2ed in AppDelegate.test() AppDelegate.swift:43
    belozierov#10 0x108ebdd77 in AppDelegate.application(_:didFinishLaunchingWithOptions:) AppDelegate.swift:18
    belozierov#11 0x108ebe4b7 in @objc AppDelegate.application(_:didFinishLaunchingWithOptions:) <compiler-generated>
    belozierov#12 0x7fff2509a592 in -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]+0xd5 (UIKitCore:x86_64+0xcd7592)
    belozierov#13 0x7fff2509c283 in -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:]+0x101f (UIKitCore:x86_64+0xcd9283)
    belozierov#14 0x7fff250a1c23 in -[UIApplication _runWithMainScene:transitionContext:completion:]+0x4a5 (UIKitCore:x86_64+0xcdec23)
    belozierov#15 0x7fff246124a2 in -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:]+0xb2 (UIKitCore:x86_64+0x24f4a2)
    belozierov#16 0x7fff2509e2ac in -[UIApplication _compellApplicationLaunchToCompleteUnconditionally]+0x3a (UIKitCore:x86_64+0xcdb2ac)
    belozierov#17 0x7fff2509e64b in -[UIApplication _run]+0x391 (UIKitCore:x86_64+0xcdb64b)
    belozierov#18 0x7fff250a32b4 in UIApplicationMain+0x64 (UIKitCore:x86_64+0xce02b4)
    belozierov#19 0x7fff59d55cc1 in UIApplicationMain(_:_:_:_:)+0x61 (libswiftUIKit.dylib:x86_64+0x21cc1)
    belozierov#20 0x108ebf357 in static UIApplicationDelegate.main() <compiler-generated>
    belozierov#21 0x108ebf2a0 in static AppDelegate.$main() AppDelegate.swift:11
    belozierov#22 0x108ebfc77 in main <compiler-generated>
    belozierov#23 0x1090f8f20 in start_sim+0x9 (dyld_sim:x86_64+0x1f20)
    belozierov#24 0x1125f352d  (<unknown module>)

0x00010f029ec1 is located 192193 bytes inside of 200704-byte region [0x00010effb000,0x00010f02c000)
allocated by thread T0 here:
    #0 0x1094c7bb3 in wrap_posix_memalign+0xb3 (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x43bb3)
    belozierov#1 0x7fff30c9e62c in swift_slowAlloc+0x4c (libswiftCore.dylib:x86_64+0x30762c)
    belozierov#2 0x109ede243 in CoroutineContext.init(stackSize:guardPage:) CoroutineContext.swift:32
    belozierov#3 0x109eddc1f in CoroutineContext.__allocating_init(stackSize:guardPage:) CoroutineContext.swift
    belozierov#4 0x109efe985 in SharedCoroutineQueue.init(stackSize:) SharedCoroutineQueue.swift:28
    belozierov#5 0x109efe5c3 in SharedCoroutineQueue.__allocating_init(stackSize:) SharedCoroutineQueue.swift
    belozierov#6 0x109efcb3d in SharedCoroutineDispatcher.getFreeQueue() SharedCoroutineDispatcher.swift:38
    belozierov#7 0x109efc20d in closure belozierov#1 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:27
    belozierov#8 0x109ee7ed9 in OS_dispatch_queue.scheduleTask(_:) CoroutineScheduler+DispatchQueue.swift:16
    belozierov#9 0x109ee88ad in protocol witness for CoroutineScheduler.scheduleTask(_:) in conformance OS_dispatch_queue <compiler-generated>
    belozierov#10 0x109efbf48 in SharedCoroutineDispatcher.execute(on:task:) SharedCoroutineDispatcher.swift:26
    belozierov#11 0x109ee3616 in CoroutineScheduler._startCoroutine(_:) CoroutineScheduler.swift:51
    belozierov#12 0x109ee3c78 in CoroutineScheduler.startCoroutine(in:task:) CoroutineScheduler.swift:69
    belozierov#13 0x108ebe2ed in AppDelegate.test() AppDelegate.swift:43
    belozierov#14 0x108ebdd77 in AppDelegate.application(_:didFinishLaunchingWithOptions:) AppDelegate.swift:18
    belozierov#15 0x108ebe4b7 in @objc AppDelegate.application(_:didFinishLaunchingWithOptions:) <compiler-generated>
    belozierov#16 0x7fff2509a592 in -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]+0xd5 (UIKitCore:x86_64+0xcd7592)
    belozierov#17 0x7fff2509c283 in -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:]+0x101f (UIKitCore:x86_64+0xcd9283)
    belozierov#18 0x7fff250a1c23 in -[UIApplication _runWithMainScene:transitionContext:completion:]+0x4a5 (UIKitCore:x86_64+0xcdec23)
    belozierov#19 0x7fff246124a2 in -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:]+0xb2 (UIKitCore:x86_64+0x24f4a2)
    belozierov#20 0x7fff2509e2ac in -[UIApplication _compellApplicationLaunchToCompleteUnconditionally]+0x3a (UIKitCore:x86_64+0xcdb2ac)
    belozierov#21 0x7fff2509e64b in -[UIApplication _run]+0x391 (UIKitCore:x86_64+0xcdb64b)
    belozierov#22 0x7fff250a32b4 in UIApplicationMain+0x64 (UIKitCore:x86_64+0xce02b4)
    belozierov#23 0x7fff59d55cc1 in UIApplicationMain(_:_:_:_:)+0x61 (libswiftUIKit.dylib:x86_64+0x21cc1)
    belozierov#24 0x108ebf357 in static UIApplicationDelegate.main() <compiler-generated>
    belozierov#25 0x108ebf2a0 in static AppDelegate.$main() AppDelegate.swift:11
    belozierov#26 0x108ebfc77 in main <compiler-generated>
    belozierov#27 0x1090f8f20 in start_sim+0x9 (dyld_sim:x86_64+0x1f20)
    belozierov#28 0x1125f352d  (<unknown module>)

SUMMARY: AddressSanitizer: stack-buffer-overflow (libclang_rt.asan_iossim_dynamic.dylib:x86_64+0x1977c) in wrap_memcpy+0x16c
Shadow bytes around the buggy address:
  0x0001344ad380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad3a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad3b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad3c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0001344ad3d0: 00 00 00 00 f1 f1 f1 f1[01]f3 f3 f3 00 00 00 00
  0x0001344ad3e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad3f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0001344ad420: 00 00 00 00 00 00 00 00 00 00 00 00 ca ca ca ca
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==39856==ABORTING
AddressSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
(lldb)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants