-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a message sender mailbox processor, wire it with the message syst…
…em (#29)
- Loading branch information
Showing
4 changed files
with
59 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
module Emulsion.MessageSender | ||
|
||
open System | ||
|
||
type MessageSenderContext = { | ||
send: OutgoingMessage -> Async<unit> | ||
logError: Exception -> unit | ||
cooldown: TimeSpan | ||
} | ||
|
||
let rec private sendRetryLoop ctx msg = async { | ||
try | ||
do! ctx.send msg | ||
with | ||
| ex -> | ||
ctx.logError ex | ||
do! Async.Sleep(int ctx.cooldown.TotalMilliseconds) | ||
return! sendRetryLoop ctx msg | ||
} | ||
|
||
let activity(ctx: MessageSenderContext): MailboxProcessor<OutgoingMessage> = MailboxProcessor.Start(fun inbox -> | ||
let rec loop() = async { | ||
let! msg = inbox.Receive() | ||
do! sendRetryLoop ctx msg | ||
return! loop() | ||
} | ||
loop() | ||
) | ||
|
||
let send(activity: MailboxProcessor<OutgoingMessage>): OutgoingMessage -> unit = activity.Post |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,54 @@ | ||
module Emulsion.MessageSystem | ||
|
||
open System | ||
open System.Collections.Concurrent | ||
open System.Threading | ||
|
||
type IncomingMessageReceiver = IncomingMessage -> unit | ||
|
||
/// The IM message queue. Manages the underlying connection, reconnects when necessary, stores the outgoing messages in | ||
/// a queue and sends them when possible. Redirects the incoming messages to a function passed when starting the queue. | ||
type IMessageSystem = | ||
/// Starts the IM connection, manages reconnects. On cancellation could either throw OperationCanceledException or | ||
/// return a unit. | ||
abstract member Run : CancellationToken -> IncomingMessageReceiver -> unit | ||
/// Starts the IM connection, manages reconnects. Never terminates unless cancelled. | ||
abstract member Run : IncomingMessageReceiver -> CancellationToken -> unit | ||
|
||
/// Queues the message to be sent to the IM system when possible. | ||
abstract member PutMessage : OutgoingMessage -> unit | ||
|
||
[<AbstractClass>] | ||
type MessageSystemBase() = | ||
let queue = ConcurrentQueue<OutgoingMessage>() | ||
abstract member Run : CancellationToken -> ConcurrentQueue<OutgoingMessage> -> IncomingMessageReceiver -> unit | ||
interface IMessageSystem with | ||
member this.Run token receiver = | ||
this.Run token queue receiver | ||
member __.PutMessage message = | ||
queue.Enqueue message | ||
|
||
type RestartContext = { | ||
token: CancellationToken | ||
cooldown: TimeSpan | ||
logError: Exception -> unit | ||
logMessage: string -> unit | ||
} | ||
|
||
let wrapRun (ctx: RestartContext) (run: CancellationToken -> unit) : unit = | ||
while not ctx.token.IsCancellationRequested do | ||
let wrapRun (ctx: RestartContext) (token: CancellationToken) (run: CancellationToken -> unit) : unit = | ||
while not token.IsCancellationRequested do | ||
try | ||
run ctx.token | ||
run token | ||
with | ||
| :? OperationCanceledException -> () | ||
| ex -> | ||
ctx.logError ex | ||
ctx.logMessage <| sprintf "Waiting for %A to restart" ctx.cooldown | ||
Thread.Sleep ctx.cooldown | ||
|
||
[<AbstractClass>] | ||
type MessageSystemBase(restartContext: RestartContext) as this = | ||
let sender = MessageSender.activity { | ||
send = this.Send | ||
logError = restartContext.logError | ||
cooldown = restartContext.cooldown | ||
} | ||
|
||
/// Starts the IM connection, manages reconnects. On cancellation could either throw OperationCanceledException or | ||
/// return a unit. | ||
abstract member Run : IncomingMessageReceiver -> CancellationToken -> unit | ||
|
||
/// Sends a message through the message system. Free-threaded. Could throw exceptions; if throws an exception, then | ||
/// will be restarted later. | ||
abstract member Send : OutgoingMessage -> Async<unit> | ||
|
||
interface IMessageSystem with | ||
member ms.Run receiver token = | ||
wrapRun restartContext token (this.Run receiver) | ||
member __.PutMessage message = | ||
MessageSender.send sender message |