0

I am writing to a socket in a threaded server (running on MRI at the moment). Doing it with the following code:

begin
  num_bytes_written = socket.write_nonblock(chunk)
  if num_bytes_written < chunk.bytesize
    chunk = chunk[num_bytes_written..-1]
    raise Errno::EINTR
  end
rescue IO::WaitWritable, Errno::EINTR
  Thread.pass if server_is_threaded
  IO.select(nil, [socket])
  retry
rescue Errno::EPIPE
  return
end

The gist of it is that I want the server that runs this to preempt another thread if I have got a WaitWritable (socket is saturated). Is Thread.pass a good idea here, or will MRI automatically preempt something else if my thread is doing a select()?

Julik
  • 7,676
  • 2
  • 34
  • 48
  • Doing `IO.select` has the same effect as `Thread.pass` if the select call is blocking. – Casper Jun 20 '15 at 11:41
  • I was thinking it is, but cannot find any documentation on IO.select vs thread scheduling - or I haven't looked deep/far enough – Julik Jun 20 '15 at 12:28
  • I don't know if it's officially documented anywhere other than some Rails books or blog posts that discuss the MRI threading model. But it's a fact with MRI; blocking IO operations will cause a thread switch. Otherwise you wouldn't even be able to do the most rudimentary threading with Ruby. Just try and read from stdin for example in one thread and do something else in another and you will see it works fine. – Casper Jun 21 '15 at 11:39

1 Answers1

1

A thread always runs until it has exceeded its time slice or goes to waiting state. On systems that support thread priorities and preempting, a higher priority thread may also be able preempt a running thread with lower priority but that depends on the system. Going to waiting state means that a thread is waiting for some event to happen and won't be available for running until that event has happened, e.g. an I/O event or a timing event like sleeping for a certain amount of time.

Thread.pass is what most other programming languages and operation systems call yielding but of course, using that term would be very misleading in Ruby for obvious reasons. It means that you tell the operation system to treat your thread as if its time slice was exceeded, giving other threads a chance to run immediately without having to wait for your thread to use up all of its time slice, to wait, or to be preempted. As your thread stays in run-able state when yielding (it is not considered waiting for anything), it is immediately available to run again, so it's not said that another thread will take over as the thread scheduler may consider your thread to still be the best candidate when deciding which thread to run next (especially if all other run-able threads have a lower priority).

But in case IO.select has to block, this will put your thread into a waiting state and then it won't be available for running anymore, so in that case the thread scheduler has to pick another thread anyway and this is documented:

Calls select(2) system call. It monitors given arrays of IO objects, waits until one or more of IO objects are ready for reading, are ready for writing, and have pending exceptions respectively, and returns an array that contains arrays of those IO objects.

Waiting always means the thread stops running and whenever a thread stops running, some other run-able thread will take over.

But if your intention is to always give another thread a chance to run in the case of an error, no matter if select would block or not (as it doesn't have to block, up to the point select is called, the socket may be writable again and then it won't block), then it may really make sense to call Thread.pass. Yet most programmers would probably not call it in that situation as if select won't block, you usually want the write operation to be repeated again immediately (within the same time slice, if possible and if not, your thread will pass anyway).

Mecki
  • 125,244
  • 33
  • 244
  • 253
  • Yes, basically `select` implies a `UBF_IO` as I later figured out. The problem I found is that I have too many threads actually, and what I need instead is a reactor-based evented IO approach. The idiom in the original question has been actually wrapped in `io/wait` in the Ruby stdlib on recent versions. – Julik Apr 22 '19 at 00:03