-
Notifications
You must be signed in to change notification settings - Fork 16
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
New option max_fork to allow allow simultaneously (parallel) session handling (multi core) #42
Comments
Implementation of the preforking feature directly handled by midi-smtp-server library. Append an additional config option like (proposal): # +max_processings+:: maximum number of simultaneous processed connections PER CORE/FORK, this does not limit the number of concurrent TCP connections. Default value = DEFAULT_SMTPD_MAX_PROCESSINGS
# +max_connections+:: maximum number of connections PER CORE/FORK, this does limit the number of concurrent TCP connections (not set or nil => unlimited)
# +max_fork+:: maximum number of child processes (FORK) to handle connections preforked and in parallel on multiple processes/cores orchestrated by operating system (not set or nil => single process) |
Hey @Davidk01 once implemented would you be so kind help testing things working like expected? Cheers, |
Hi @gencer Maybe an interesting feature for your environment as well. Cheers, |
Yes, This can be considered on my environment as you already know what I am doing. I believe this is NOT implemented yet, right? P.S.: Sorry for the late reply. I got the message same day as you send and already added to my tasks (backlog). I just away from GitHub for a while (except few things) but I'm back now 🥳 Thanks, |
Great to hear from you! Awaiting your update here. 🥇 Gencer. |
Hi @Davidk01 I have finished the implementation and be currently doing some testing. Therefor I run into a problem / case while testing on OSX (my dev system) The Sockets are not balanced between the forked processes. Instead having a clean pid 1-2-3-4 1-2-3-4 1-2-3-4 situation I got some random like 1-1-1-2 2-1-3-3 1-1-3-4 and so on. I have searched over the internet and found this article: https://relaxdiego.com/2017/02/load-balancing-sockets.html On OSX it is not working as expected. I opened an issue to the author at: relaxdiego/relaxdiego.github.com#30 Could you please step in and try the simple ruby source and test from the page. While there is a bug in the example, please try this code: #!/usr/bin/env ruby
require 'socket'
# Open a socket
socket = TCPServer.open('0.0.0.0', 9999)
puts "Server started ..."
# For keeping track of children pids
wpids = []
# Forward any relevant signals to the child processes.
[:INT, :QUIT].each do |signal|
Signal.trap(signal) {
wpids.each { |wpid| Process.kill(:KILL, wpid) }
}
end
5.times {
wpids << fork do
loop {
connection = socket.accept
connection.puts "Hello from #{ Process.pid }"
connection.close
}
end
}
Process.waitall and this in a second terminal for testing connections when server is started: for i in {1..10}; do nc -d localhost 9999; done Thanks for sharing your results. Tom P.S.: @gencer: Hi :-), could you please try once the same test? |
fix: spelling comments fix: use exception class for raise 421 abort
I have checked in the new pre-release with enhancement of pre-fork. To use please have a look at https://github.com/4commerce-technologies-AG/midi-smtp-server/blob/master/examples/midi-smtp-server-pre-fork-example.rb and take the changes in Any comment is welcome. @gencer the component should work as usual without any breakings when not touching parameter |
Thanks @TomFreudenberg. I'll take a look later tonight and follow up but that examples looks ok to me. Thanks for putting it together. |
Please be aware of the posted issue on MacOSX - also asked on StackOverflow https://stackoverflow.com/questions/73704741/ruby-strange-forked-processes-behaviour-on-macos-vs-debian Unless there is a reliable solution, it is not possible to handle a valid usage limitation adjusted by On a Windows system |
Thanks Tom, this is good to know. I always assumed the distribution would be fair but it looks like that's not the case. I tested in a virtual machine and there is definitely some imbalance in how the requests are distributed. Below are the results for
|
On my environment, Linux is the only OS used with MidiSmtpServer. Therefore, I'll not be able to test it on OSX or Win (as already fork is not available). @TomFreudenberg Thanks for those updates I'll test tonight and let you know the results. 🥳 |
Hi all, in case of not "fair balancing" the processes from OS, I consider to implement a communication pipe or unix socket between the parent and the child processes to handle a valid load usage limitation. @Davidk01 some experiences in that? Here a some links to share and for information:
As from first reading I will have a look on Any other suggestions so far? |
In addition to the terms I will change the internal wording from |
From the above So I will follow the approach from the linked gist and create an coordinator thread with unix sockets when activating pre-fork. That will only be used to communicate the current load and connections of the master. It enables to control the system load by |
This sounds very familiar to a load balancer but I don't have much experience with implementing one in Ruby with zeromq. I often use HAProxy for that use case so their documentation might be helpful: http://www.haproxy.org/. |
For interprocess communication without any dependencies dRuby might be a good option: https://www.druby.org/sidruby/the-druby-book.html. |
@Davidk01 drb looks pretty nice for our case and is already there in standard. drbunix:// uri allows usage of unix sockets so that communication between processes is fast. Not sure how much speed is taken from overhead of marshalling objects between the processes, maybe raw UNIXSockets is a bit faster. Will try to do some benchmarks for that. |
Thousands of emails processed without any issue! (Linux only) |
Great, thanks @gencer I guess I will push the update with the coordinator thread around weekend. Will let you know when things are available. |
|
From @Davidk01
Describe the changes
This change adds an extra parameter to skip starting the accepting thread. The reason is that I would like to bind the TCP connection, fork several processes, and then in each process start the accept loops. This pattern is often used to let the operating system load balance connections to several processes without the need of proxies like HAProxy.
Test case
The change is backwards compatible but the way to test that it works would be to initialize the server and then start the accepting threads in forked processes, e.g.
Expected behavior
Previous functionality should continue to work as expected and new functionality should be enabled as described above by passing in a parameter to skip starting the acceptance thread.
System tested on (please complete the following information):
Additional information
This is a draft proposal so if there are any changes that need to be made please let me know and I'm happy to make them. Being able to fork and then accept connections would be very useful for reducing operational overhead like deploying HAProxy so I'm motivated to get this merged, I think it would be helpful for other people as well who are interested in deploying a Ruby SMTP server to simplify the management of email servers and workloads.
Here is a blog post with a good explanation about pre-forking servers that accept connections in child processes: Linux Applications Performance: Part III: Preforked Servers. The relevant section for this PR is the following:
So in pseudo-code this is what happens:
In each child process the number of active connection is respected the same way it would have been in the parent process if it was running by itself but the benefit of the pre-forking model is that once the parent process binds to the socket then that socket can be shared among any number of child processes handling the accept loop and the operating system will fairly distribute the connections among the forked processes. Ruby has a global interpreter lock so if a server has 10 cores then to fully utilize all the cores will require spawning more than one process to listen for connections because threads are not mapped to a process. Here is a good article explaining some of the implementation details of Ruby's threads: Ruby threads worth it?:
Current implementation limits
In the moment midi-smtp-server will use 1 (one) core only regardless an available multi-core cpu and running threads per this one CORE only, handled by Ruby GIL and executed one thread action after next thread action.
Implementation target
We want to append an option to enable more cores by forking processes and let them handle a number of threads per each core (forked process) to handle simultaneously (parallel) sessions.
The text was updated successfully, but these errors were encountered: