F# Server side programming
Dave ThomasMoiraeSoftware.comtwitter.com/7sharp9
F# Server based programming
Why F#• Concise succinct code
• Advanced asynchronous support
• Language Orientated Programming
• Generic by default
• Immutable by default
Fundamental Concepts
• Pure Functions– No side effects– memoization
• Immutability– No threading issues
• Lambda expressions• Higher order functions• Recursion
Patterns and Practices
C#• Inheritance• Polymorphism
F#• Higher order functions• Type augmentation
Patterns / similarities
• Discriminated unions -> Small class hierarchies
• Higher Order Functions -> Command Pattern
• Fold -> Visitor pattern
A comparison of styles
F#• 1 source file• 43 lines• 2 types
C#• 2 source files• 166 lines• 2 type
75% reduction in code
C# ObjectPool
F# ObjectPool//Agent alias for MailboxProcessortype Agent<'T> = MailboxProcessor<'T>
///One of three messages for our Object Pool agenttype PoolMessage<'a> = | Get of AsyncReplyChannel<'a> | Put of 'a | Clear of AsyncReplyChannel<List<'a>>
/// Object pool representing a reusable pool of objectstype ObjectPool<'a>(generate: unit -> 'a, initialPoolCount) = let initial = List.init initialPoolCount (fun (x) -> generate()) let agent = Agent.Start(fun inbox -> let rec loop(x) = async { let! msg = inbox.Receive() match msg with | Get(reply) -> let res = match x with | a :: b -> reply.Reply(a);b | [] as empty-> reply.Reply(generate());empty return! loop(res) | Put(value)-> return! loop(value :: x) | Clear(reply) -> reply.Reply(x) return! loop(List.empty<'a>) } loop(initial))
/// Clears the object pool, returning all of the data that was in the pool. member this.ToListAndClear() = agent.PostAndAsyncReply(Clear)
/// Puts an item into the pool member this.Put(item ) = agent.Post(item)
/// Gets an item from the pool or if there are none present use the generator member this.Get(item) = agent.PostAndAsyncReply(Get)
F# - Fracture IO
Open source high performance Socket, Pipeline, and agent library
Sockets
2 Models of Operation
• AsyncResult Model
• SocketAsyncEventArgs Model
IAsyncResults ModelPros• Well documented API• Can be simplified with helpers in
Asynchronous workflows in F#• Fast to get an initial result
Cons• Allocation of IAsyncResult on every Operation• Consumes more CPU for each send and receive operation
F# AsyncResult
SocketAsyncEventArgs Model
Pros• Less memory allocations• Better performance• Closer to the metal
Cons• Not a well understood or documented API• Can be complex to get right
Performance Differences
• 5 minute test
• 50 clients connecting to the server
• 15ms interval between each one
• Server sends each client a 128 byte message every 100ms
• Total of 500 messages per second
Test Harness
CPU & Threads
SocketAsyncEventArgs IAsyncResult
MemorySocketAsyncEventArgs IAsyncResult
Garbage collectionSocketAsyncEventArgs IAsyncResult
Fracture Socket Implementation
Agents
• Provide a message passing mechanism
• Agents read and respond to a queue of messages
• Typically a discriminated union is used to describe messages
Introducing Fracture-IO
• High performance Socket library
• Highly compositional pipeline library
Pipelines
• Closed Pipelines– Tomas Petricek• Image Processing Pipeline
• Open Pipelines– Pipelets
Closed Pipelines
Image Processing Pipeline
1: // Phase 1: Load images from disk and put them into a queue 2: let loadImages = async { 3: let clockOffset = Environment.TickCount 4: let rec numbers n = seq { yield n; yield! numbers (n + 1) } 5: for count, img in fileNames |> Seq.zip (numbers 0) do 6: let info = loadImage img sourceDir count clockOffset 7: do! loadedImages.AsyncAdd(info) } 8: 9: // Phase 2: Scale to a thumbnail size and add frame10: let scalePipelinedImages = async {11: while true do 12: let! info = loadedImages.AsyncGet()13: scaleImage info14: do! scaledImages.AsyncAdd(info) }
1: let loadedImages = new BlockingQueueAgent<_>(queueLength)2: let scaledImages = new BlockingQueueAgent<_>(queueLength) 3: let filteredImages = new BlockingQueueAgent<_>(queueLength)
1: // Phase 4: Display images as they become available 2: let displayPipelinedImages = async { 3: while true do 4: let! info = filteredImages.AsyncGet() 5: info.QueueCount1 <- loadedImages.Count 6: info.QueueCount2 <- scaledImages.Count 7: info.QueueCount3 <- filteredImages.Count 8: displayImage info } 9: 10: // Start workflows that implement pipeline phases11: Async.Start(loadImages, cts.Token)12: Async.Start(scalePipelinedImages, cts.Token)13: Async.Start(filterPipelinedImages, cts.Token)14: try Async.RunSynchronously(displayPipelinedImages, cancellationToken = cts.Token)15: with :? OperationCanceledException -> ()
Open Pipelines
Advantages• Composability• Reusability• Can be used at any stage of processing• Separation of concerns• Flexible routing arrangements i.e. round robin, multicast,
content based routing
Disadvantages• Pipeline stages need to be balanced• Developers can have trouble debugging and visualizing
Pipelets Implementation
Pipelets in use
Future Directions
• Distributed Pipelets– Wave Pipelines
– Synchronous buffered Pipeline
• Advanced agent based programming- Supervision
- Pooling
- Control Messages