#!/usr/bin/env ruby
# STAQ threading & forking training 4/26/16
#
# Invoke like so:
#
# ruby forking_and_threading.rb
# ruby forking_and_threading.rb thread
# ruby forking_and_threading.rb naive # seems to work OK
# env RBENV_VERSION=jruby-9.0.4.0 ruby ./forking_and_threading.rb naive # why does this give different results?
#
# Can monitor the processes being created with this command:
# watch -n 0.2 'ps -o command,pid,ppid,rss  | grep ruby | grep -v grep'

flag = ARGV[0]

case flag
when nil
  puts "Watch processes with this command:\nwatch -n 0.2 'ps -o pid,ppid,command  | grep ruby | grep -v grep'"
  puts "*" * 30

  puts "I'm the parent process, and my process ID is #{Process.pid}"

  pid = Process.fork {
    puts "\tI'm a child process, and my process ID is #{Process.pid}"
    sleep 20
  }

  puts "I'm the parent process, and my process ID is #{Process.pid}. I'm aware of the fact that I have a child process #{pid}"

  Process.wait # Registers interest in the subprocess' state, so as to avoid a zombie process; will block
when "spawn"
  pid = spawn({ "STAQ_VAL" => "100" }, RbConfig.ruby,%q|-e puts "\tHello from spawned process #{Process.pid} with STAQ_VAL of #{ENV.fetch("STAQ_VAL")}"; sleep 30|)
  puts "I'm parent process #{Process.pid} and I just spawned process #{pid}. Now I am detaching."
  Process.detach(pid) # Creates a thread responsible for reaping the identified process; see http://ruby-doc.org/core-2.2.0/Process.html#method-c-detach
when "thread"
  require "thread"

  queue = Queue.new # this is a thread-safe object to share between threads

  threads = 5.times.map do |time|
    Thread.new do
      val = queue.pop
      sleep rand(1..3)
      puts "I'm in process #{Process.pid} and my thread number is #{time}. My thread ID is #{Thread.current.object_id}. I just received queue item #{val}"
    end
  end

  (100..500).step(100).each do |work|
    queue.push(work)
  end

  threads.each(&:join) # wait for all threads to finish
when "naive"
  # This is not a great example, because MRI will actually produce consistent results, because we're relying on the GIL.
  # But when you run this in an interpreter without GIL (like JRuby or Rubinius) you can see the problem. It's hard to
  # create a simple scenario in MRI to clearly show the issue. Usually threading problems in MRI are more pernicious for
  # this reason!
  $bank_account_a = 0
  $bank_account_b = 0

  threads = 2000.times.map do
    Thread.new do
      # NEVER DO THIS
      $bank_account_a += 1
      $bank_account_b += 1
    end
  end

  threads.each(&:join)

  # Will get inconsistent answers for these
  puts $bank_account_a
  puts $bank_account_b
when "mutex"
  # Will produce consistent results under any interpreter

  require "thread"

  mutex = Mutex.new

  $bank_account_a = 0
  $bank_account_b = 0

  threads = 2000.times.map do
    Thread.new do
      mutex.synchronize do
        $bank_account_a += 1
        $bank_account_b += 1
      end
    end
  end

  threads.each(&:join)

  # Will get inconsistent answers for these
  puts $bank_account_a
  puts $bank_account_b
end