Skip to content

Commit

Permalink
(#141) EmulsionXmpp: add connection timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
ForNeVeR committed Sep 25, 2021
1 parent 68649ea commit d08d20b
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 2 deletions.
1 change: 1 addition & 0 deletions Emulsion.Tests/SettingsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ let private testConfiguration = {
Room = "room"
RoomPassword = None
Nickname = "nickname"
ConnectionTimeout = TimeSpan.FromMinutes 5.0
MessageTimeout = TimeSpan.FromSeconds 30.0
PingInterval = None
PingTimeout = TimeSpan.FromSeconds 30.0
Expand Down
40 changes: 40 additions & 0 deletions Emulsion.Tests/Xmpp/EmulsionXmppTests.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Emulsion.Tests.Xmpp.EmulsionXmppTests

open System
open System.Diagnostics
open System.Threading
open System.Threading.Tasks

open JetBrains.Lifetimes
Expand All @@ -22,6 +24,7 @@ let private settings = {
Room = "room@conference.example.org"
RoomPassword = None
Nickname = "nickname"
ConnectionTimeout = TimeSpan.FromSeconds 30.0
MessageTimeout = TimeSpan.FromSeconds 30.0
PingInterval = None
PingTimeout = defaultPingTimeout
Expand Down Expand Up @@ -70,6 +73,43 @@ type RunTests(outputHelper: ITestOutputHelper) =
|> ignore
Assert.Equal((settings.Room, settings.Nickname), joinRoomArgs)

[<Fact>]
member _.``EmulsionXmpp cancels the connection after timeout``(): unit =
let timeout = TimeSpan.FromSeconds 1.0
let settings = {
settings with
ConnectionTimeout = timeout
}
let client =
XmppClientFactory.create(
connect = fun () -> async {
do! Async.Sleep(timeout * 10.0)
}
)
let sw = Stopwatch.StartNew()
Assert.Throws<TimeoutException>(fun () -> runClientSynchronously settings logger client ignore)
|> ignore
Assert.True(sw.Elapsed < timeout * 2.0)

[<Fact>]
member _.``EmulsionXmpp forcibly terminates the connection after timeout * 3``(): unit =
let timeout = TimeSpan.FromSeconds 1.0
let settings = {
settings with
ConnectionTimeout = timeout
}
let client =
XmppClientFactory.create(
connect = fun () -> async {
Thread.Sleep(timeout * 10.0) // non-cancellable
}
)
let sw = Stopwatch.StartNew()
Assert.Throws<OperationCanceledException>(fun () -> runClientSynchronously settings logger client ignore)
|> ignore
Assert.True(sw.Elapsed > timeout)
Assert.True(sw.Elapsed < timeout * 6.0)

type ReceiveMessageTests(outputHelper: ITestOutputHelper) =
let logger = Logging.xunitLogger outputHelper

Expand Down
3 changes: 3 additions & 0 deletions Emulsion/Settings.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type XmppSettings = {
Room: string
RoomPassword: string option
Nickname: string
ConnectionTimeout: TimeSpan
MessageTimeout: TimeSpan
PingInterval: TimeSpan option
PingTimeout: TimeSpan
Expand All @@ -31,6 +32,7 @@ type EmulsionSettings = {
Log: LogSettings
}

let defaultConnectionTimeout = TimeSpan.FromMinutes 5.0
let defaultMessageTimeout = TimeSpan.FromMinutes 5.0
let defaultPingTimeout = TimeSpan.FromSeconds 30.0

Expand All @@ -50,6 +52,7 @@ let read (config : IConfiguration) : EmulsionSettings =
Room = section.["room"]
RoomPassword = Option.ofObj section.["roomPassword"]
Nickname = section.["nickname"]
ConnectionTimeout = readTimeSpan defaultConnectionTimeout "connectionTimeout" section
MessageTimeout = readTimeSpan defaultMessageTimeout "messageTimeout" section
PingInterval = readTimeSpanOpt "pingInterval" section
PingTimeout = readTimeSpan defaultPingTimeout "pingTimeout" section
Expand Down
40 changes: 38 additions & 2 deletions Emulsion/Xmpp/EmulsionXmpp.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// Main business logic for an XMPP part of the Emulsion application.
module Emulsion.Xmpp.EmulsionXmpp

open System
open JetBrains.Lifetimes
open Serilog
open SharpXMPP.XMPP
Expand Down Expand Up @@ -36,14 +37,49 @@ let initializeLogging (logger: ILogger) (client: IXmppClient): IXmppClient =
)
client

let private withTimeout<'a> title (logger: ILogger) (workflow: Async<'a>) (timeout: TimeSpan) = async {
logger.Information("Starting \"{Title}\" with timeout {Timeout}.", title, timeout)
let! child = Async.StartChild(workflow, int timeout.TotalMilliseconds)

let! childWaiter = Async.StartChild(async {
let! _ = child
return Some true
})

let waitTime = timeout * 1.5
let timeoutWaiter = async {
do! Async.Sleep waitTime
return Some false
}

let! completedInTime = Async.Choice [| childWaiter; timeoutWaiter |]
match completedInTime with
| Some true -> return! child
| _ ->
logger.Information(
"Task {Title} neither complete nor cancelled in {Timeout}. Entering extended wait mode.",
title,
waitTime
)
let! completedInTime = Async.Choice [| childWaiter; timeoutWaiter |]
match completedInTime with
| Some true -> return! child
| _ ->
logger.Warning(
"Task {Title} neither complete nor cancelled in another {Timeout}. Trying to cancel forcibly by terminating the client.",
title,
waitTime
)
return raise <| OperationCanceledException($"Operation \"%s{title}\" forcibly cancelled")
}

/// Outer async will establish a connection and enter the room, inner async will await for the room session
/// termination.
let run (settings: XmppSettings)
(logger: ILogger)
(client: IXmppClient)
(messageReceiver: IncomingMessageReceiver): Async<Async<unit>> = async {
logger.Information "Connecting to the server"
let! sessionLifetime = connect client
let! sessionLifetime = withTimeout "server connection" logger (connect client) settings.ConnectionTimeout
sessionLifetime.ThrowIfNotAlive()
logger.Information "Connection succeeded"

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ settings, there're defaults:
{
"xmpp": {
"roomPassword": null,
"connectionTimeout": "00:05:00",
"messageTimeout": "00:05:00",
"pingInterval": null,
"pingTimeout": "00:00:30"
Expand Down
1 change: 1 addition & 0 deletions emulsion.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"room": "xxxxx@conference.example.org",
"roomPassword": "// optional",
"nickname": "хортолёт",
"connectionTimeout": "00:05:00",
"messageTimeout": "00:05:00",
"pingInterval": "00:01:00",
"pingTimeout": "00:00:05"
Expand Down

0 comments on commit d08d20b

Please sign in to comment.