07 Mar, 2009, JohnnyStarr wrote in the 1st comment:
Votes: 0
Hey guys,
I'm still learning ruby and loving it, i'm using it to start a
new code base (mainly for learning purposes) and was wondering if / how threadding would come in to play. I noticed that RocketMud appears to be single threaded. I got this code online and was wondering how this could be implemented:
require "socket"  
dts = TCPServer.new('localhost', 20000)
loop do
Thread.start(dts.accept) do |s|
print(s, " is accepted\n")
s.write(Time.now)
print(s, " is gone\n")
s.close
end
end
07 Mar, 2009, Vassi wrote in the 2nd comment:
Votes: 0
See this thread:
http://mudbytes.net/index.php?a=topic&am...

I think it covers most of the bases.
07 Mar, 2009, David Haley wrote in the 3rd comment:
Votes: 0
Yes, the long answer to your question is in that thread. The short answer is that you probably really don't want to get involved in threading because it is very complicated and you have to really know what you're doing to get advantages from it, and you also have to really know what you're doing to not bring down your program in flames. :smile:
07 Mar, 2009, JohnnyStarr wrote in the 4th comment:
Votes: 0
Well thats cool, i'll have to read up on it.
I do understand threads are in depth in themselves, but my question wasn't as detailed as the other post (thread).

My question was about how the sockets handle incomming connections. Some sites have shown examples of using the built in TCPServer in the 'socket' module. In RocketMud it uses what i believe is called 'non blocking sockets' which runs on a single thread. The code posted earlier adds a new thread for each socket right? David's website about his mud project has tons and tons of cool things it does with threads and thats great and all, and i hope some day to get that advanced.

But i'm just talking about the sockets themselves. not all the layers of game logic and updates etc.
07 Mar, 2009, David Haley wrote in the 5th comment:
Votes: 0
Yes, the code you posted here launches a thread per socket. That's unlikely to be a good idea when running a MUD server, especially since you can use non-blocking sockets instead.

A blocking socket will cause the program to halt until there is something to read. A nonblocking socket will return "not now" if there's nothing to read yet, or more precisely if attempting to read would cause a block. The OS takes care of the underlying magic for you, and you should take advantage of that.

If you're talking about the BabbleMUD site, it's worth mentioning that I wrote that up in 2004 and have learned a lot since and changed my mind on several issues. In particular, when I resumed development a few weeks ago, I canned the two threads idea and will instead be using just one thread. If I do use threads, it would be for hostname resolution only, which in my experience tends to be a blocking operation that you can't really get around.
07 Mar, 2009, JohnnyStarr wrote in the 6th comment:
Votes: 0
Oh ok, thanks David. :smile:
07 Mar, 2009, Barm wrote in the 7th comment:
Votes: 0
To build on what David said, you'll call socket.select() with three lists. The first is all sockets that you want to try reading from (which will be the server socket + all clients), the second is a list of sockets that you want to send data on (every client with outgoing text), and the third is usually empty (and I completely forget what it was for). You set the timeout argument to 0 which tells select() not to wait around if there's nothing to do.

See: http://beej.us/guide/bgnet/output/html/m...

Select() spends a microsecond doing its thing and returns three lists (in Python anyway, but I'd guess Ruby does the same). The lists are those sockets that are readable, writeable, and those with errors. If one of the read sockets has the same socket number as your server then it's a new client trying to connect and you use the socket's accept() function to process it. The other read sockets will be that subset of ALL clients that are trying to send data to your server. The writeable list is (unless something is broke) going to be the same list you passed it (sockets with messages waiting to be sent to clients).

Based on my progress so far, I'm sold on a single threaded design. This is kinda abstract, but one advantage is the entire world freezes while I process a block of code. Which means if check someone's gold before I sell him an item, nothing else can change that gold before I deduct it. Or remove a monster before I place a spell on it, etc. The disadvantage is … the entire world freezes while I process a block of code. So I can't afford long processes. Plus my design has to be event driven. I can't write a function like "get_name()" that prompts the player and waits on input.

edit typo
07 Mar, 2009, quixadhal wrote in the 8th comment:
Votes: 0
David Haley said:
Yes, the code you posted here launches a thread per socket. That's unlikely to be a good idea when running a MUD server, especially since you can use non-blocking sockets instead.


It's a question of tradeoffs. If each socket has it's own (blocking) thread, you will guarentee that you'll avoid input/output lag unless the entire machine is overloaded. If you use non-blocking sockets in a single thread, you may experience lag if the application itself can't keep up with processing the socket data AND doing all the other stuff the mud server has to do.

The tradeoff is that with a threaded socket system, you have to use semaphore locks to ensure that when your thread is reading/writing to the I/O buffers, the mud server can't ALSO be doing so, and visa-versa. Also, depending on your OS and how it implements threads, you may incur a decent amount of extra memory usage.

Threaded socket code is more elegant, but once you've written your socket handler, how often are you going to be looking at it again?
07 Mar, 2009, quixadhal wrote in the 9th comment:
Votes: 0
Oh, actually…. I just realized that you're using ruby. :)

There's a third option you might take a gander at…. co-processes. Full threading requires locking to ensure values are fixed when multiple threads try to access the same data, but co-processes are threads that don't multitask. That is, one thread has to yield before another can be run. In some ways, that gives you the elegance of threads without most of the headaches. I haven't dug into ruby much, but I remember seeing that as one of the features that sounded quite fun.
07 Mar, 2009, David Haley wrote in the 10th comment:
Votes: 0
quixadhal said:
If each socket has it's own (blocking) thread, you will guarentee that you'll avoid input/output lag unless the entire machine is overloaded. If you use non-blocking sockets in a single thread, you may experience lag if the application itself can't keep up with processing the socket data AND doing all the other stuff the mud server has to do.

If your processing thread is so busy as to not be able to handle I/O and ticks in the allotted time, then you wouldn't be able to generate output fast enough for the sockets anyhow using the threaded model, so the threaded model is still not appropriate.

It's all well and good to be able to get input and send output as fast as you possibly can, but that's useless if the link in the middle isn't able to process input and do something with it to generate output.

If each thread handled its own game logic updates, output generation etc., the threaded model would make sense. But remember that MUDs don't tend to work that way: you have a single update thread that does its stuff, and sends output to the sockets. Now, you don't want your sockets to cause the whole MUD to hang, so you use non-blocking sockets.

The threaded socket model here is kind of comparable to having one guy assigned to each car coming into a carwash, but then making all cars go through one washing machine before they can come out. That just doesn't make any sense: you might as well have one queue since you only have one handler.

To summarize, multiple queues are only useful if you have multiple handlers. There is no point at all giving everybody their own queue if they have to go through one common funnel anyhow.

quixadhal said:
Threaded socket code is more elegant

To be honest, I don't really agree. It's just not appropriate in this case.
07 Mar, 2009, JohnnyStarr wrote in the 11th comment:
Votes: 0
Wow, thats really in depth. But cool.
I'm starting to think that i might be best off just
using the RocketMud socket structure and just focus on
my own game logic.

I mean, i guess if i develop some better way of doing things
i could always go back and update the socket functionality.
I guess thats whats so good about public interfaces ;) :lol:
Thanks guys.
07 Mar, 2009, Grimble wrote in the 12th comment:
Votes: 0
You may want to read this comparison of io patterns. It has some interesting performance data.

I'd be interested to know if somebody has looked at the io load on a MUD server, a busy one obviously, to see if (threaded) asynchronous io is warranted. My guess would be no.
08 Mar, 2009, kiasyn wrote in the 13th comment:
Votes: 0
The Reactor Gem for ruby is rather nice too.
08 Mar, 2009, Tyche wrote in the 14th comment:
Votes: 0
TeensyMUD 2.0.0 implements threaded sockets.
The latest TeensyMUD 2.10.0 implements Schmidt's Acceptor/Connector/Reactor pat....
I've found the single select server to perform better than the threaded server.
08 Mar, 2009, Tyche wrote in the 15th comment:
Votes: 0
There's also ruby gem called EventMachine which implements a Twisted Python sort of interface. It apparently works with both threaded and unthreaded code. I don't much like the "inversion of control" those architectures provide but YMMV. And there's a ruby gem for libevent, which is used in the Mongrel web server. Supposedly, it is fast and scales.

Lastly, I'll be releasing something hopefully useful within the next month or so for C/C++ muds and a gem for Ruby.
08 Mar, 2009, Tyche wrote in the 16th comment:
Votes: 0
Barm said:
To build on what David said, you'll call socket.select() with three lists. The first is all sockets that you want to try reading from (which will be the server socket + all clients), the second is a list of sockets that you want to send data on (every client with outgoing text), and the third is usually empty (and I completely forget what it was for). You set the timeout argument to 0 which tells select() not to wait around if there's nothing to do.


If the client uses send() with the flag MSG_OOB the data will appear in the third set, unless the receiving socket has SO_OOBINLINE set with setsockopt(). If it does then all the data will come in the first set, although the OOB data appears out of sequence. It's rarely used, and not an error as the var name "errorfds" in select() might suggest, but an exceptional condition. You can trigger it with telnet clients by sending attention, cancel, and other functions, like sometimes AYT. Most Dikumuds respond by dumping the client.
08 Mar, 2009, Grimble wrote in the 17th comment:
Votes: 0
Tyche said:
Lastly, I'll be releasing something hopefully useful within the next month or so for C/C++ muds and a gem for Ruby.

There's an existing asynchronous io library for C++ here that could be leveraged for this.
08 Mar, 2009, quixadhal wrote in the 18th comment:
Votes: 0
Tyche said:
I've found the single select server to perform better than the threaded server.

That doesn't surprise me, actually. On a server where threading doesn't result in multiple processes (older versions of Solaris?), you can win with threads because the socket ones will sleep for 99.9% of their time. However, under linux, every thread is a first-class process, meaning you get all the overhead of having seperate (possibly duplicated) memory pages, a full context switch, and the scheduler treats them all as seperate scheduled entities.

What might be worth exploring is the difference between using select() and poll(). Once upon a time, people started using poll() because select() had a limit on the number of descriptors you could work with (and there used to be big muds with >256 players!), but I don't know if that limit exists nowadays, so it might just be which API you prefer.
09 Mar, 2009, Tyche wrote in the 19th comment:
Votes: 0
quixadhal said:
Tyche said:
I've found the single select server to perform better than the threaded server.

That doesn't surprise me, actually. On a server where threading doesn't result in multiple processes (older versions of Solaris?), you can win with threads because the socket ones will sleep for 99.9% of their time. However, under linux, every thread is a first-class process, meaning you get all the overhead of having seperate (possibly duplicated) memory pages, a full context switch, and the scheduler treats them all as seperate scheduled entities.


Well I meant in these particular implementations which used the ruby MRI, in which case threads aren't real threads. Well… some implementations or builds are, but there's additional overhead in any case with a lot of mutexes/critical sections in the interpreter. On some recent dev builds (ruby 1.9) use real threads without the the same level of locking overhead. It's my understanding that there will likely be two different thread models and a fiber implementation in ruby. So YMMV.

quixadhal said:
What might be worth exploring is the difference between using select() and poll(). Once upon a time, people started using poll() because select() had a limit on the number of descriptors you could work with (and there used to be big muds with >256 players!), but I don't know if that limit exists nowadays, so it might just be which API you prefer.


poll and select appear to wrap one or the other depending on the platform. There used to be a low limit on file descriptors a process could have open (early 90's) which probably doesn't apply to any modern system outside of special micro-OSs. There's a macro FD_SETSIZE which you have to define if you want a larger FD set for select, since the array is set up in the headers. Again, in the mid 90's the default was 64 on windows and most of the unixes I worked with. Today, it's probably 1024 on most systems, but you can still override that to be higher. The real limit isn't the size of the array, but the number of sockets/files your tcp stack can handle, or more likely amount of memory you've got. For example the stack in Vista can support 32K sockets open, however they are allocated in REAL memory, which is a fixed pecentage of whatever you've got plugged into your machine.
09 Mar, 2009, Barm wrote in the 20th comment:
Votes: 0
Tyche said:
If the client uses send() with the flag MSG_OOB the data will appear in the third set, unless the receiving socket has SO_OOBINLINE set with setsockopt().


I'm going to guess that OOB refers to 'Out of Band' signalling. I didn't even know that was supported at the socket layer.
0.0/29