title: Is this Ruby code using threads, thread pools, and concurrency correctly
body: <p>I am what I now consider part 3 of completing a task of pinging a very large list of URLs (which number in the thousands) and retrieving a URL's x509 certificate associated with it. Part 1 is <a href="https://stackoverflow.com/questions/60160027/how-do-i-properly-use-threads-to-connect-ping-a-url">here (How do I properly use threads to ping a URL)</a> and Part 2 is <a href="https://stackoverflow.com/questions/60175650/why-wont-my-simple-connection-pool-execute-these-simple-put-statements">here (Why won't my connection pool implement my thread code)</a>.</p>
  
  <p>Since I asked these two questions, I have now ended up with the following code:</p>
  
  <pre><code>###### This is the code that pings a url and grabs its x509 cert #####
  
  class SslClient
    attr_reader :url, :port, :timeout
  
    def initialize(url, port = '443')
      @url = url
      @port = port
    end
  
    def ping_for_certificate_info
      context = OpenSSL::SSL::SSLContext.new
      tcp_client = TCPSocket.new(url, port)
      ssl_client = OpenSSL::SSL::SSLSocket.new tcp_client, context
      ssl_client.hostname = url
      ssl_client.sync_close = true
      ssl_client.connect
      certificate = ssl_client.peer_cert
      verify_result = ssl_client.verify_result
      tcp_client.close
      {certificate: certificate, verify_result: verify_result }
    rescue =&gt; error
      {certificate: nil, verify_result: nil }
    end
  end
  </code></pre>
  
  <p>The above code is paramount that I retrieve the <code>ssl_client.peer_cert</code>. Below I have the following code that is the snippet that makes multiple HTTP pings to URLs for their certs:</p>
  
  <pre><code>  pool = Concurrent::CachedThreadPool.new
    pool.post do
      [LARGE LIST OF URLS TO PING].each do |struct|
         ssl_client = SslClient.new(struct.domain.gsub("*.", "www."), struct.scan_port)
         cert_info = ssl_client.ping_for_certificate_info
         struct.x509_cert = cert_info[:certificate]
         struct.verify_result = cert_info[:verify_result]
       end
     end
  
     pool.shutdown
     pool.wait_for_termination
  
     #Do some rails code with the database depending on the results.
  </code></pre>
  
  <p>So far when I run this code, it is unbelievably slow. I thought that by creating a thread pool with threads, the code would go much faster. That doesn't seem the case and I'm not sure why. A lot of it was because I didn't know the nuances of threads, pools, starvation, locks, etc. However, after implementing the above code, I read some more to try to speed it up and once again I'm confused and could use some clarification as to how I can make the code faster.</p>
  
  <p>For starters, in this excellent article <a href="https://www.honeybadger.io/blog/ruby-concurrency-parallelism/" rel="noreferrer">here (ruby-concurrency-parallelism)</a> . We get the following definitions and concepts:</p>
  
  <blockquote>
    <p>Concurrency vs. Parallelism
    These terms are used loosely, but they do have distinct meanings.</p>
    
    <p><strong>Concurrency</strong>: The art of doing many tasks, one at a time. By switching
    between them quickly, it may appear to the user as though they happen
    simultaneously. <strong>Parallelism</strong>: Doing many tasks at literally the same
    time. Instead of appearing simultaneous, they are simultaneous.
    <strong><em>Concurrency is most often used for applications that are IO heavy. For
    example, a web app may regularly interact with a database or make lots
    of network requests.</em></strong> By using concurrency, we can keep our application
    responsive, even while we wait for the database to respond to our
    query.</p>
    
    <p>This is possible because the Ruby VM allows other threads to run while
    one is waiting during IO. Even if a program has to make dozens of
    requests, if we use concurrency, the requests will be made at
    virtually the same time.</p>
    
    <p>Parallelism, on the other hand, is not currently supported by Ruby.</p>
  </blockquote>
  
  <p>So from this piece of the article, I understand that what I want to do needs to be done concurrently because I am pinging URLs on the network and that <strong>Parallelism is not currently supported by Ruby.</strong></p>
  
  <p>Next is where things get confused for me. From my part 1 question on Stack Overflow, I learned the following in a comment given to me that I should do the following:</p>
  
  <blockquote>
    <p>Use a thread pool; don't just create a thousand concurrent threads. For something like 
    connecting to a URL where there will be a lot of waiting you can
    oversubscribe the number of threads per CPU core, but not by a huge
    amount. You'll have to experiment.</p>
  </blockquote>
  
  <p>Another user says this:</p>
  
  <blockquote>
    <p>You'd not spawn thousands of threads, use a connection pool 
    (e.g <a href="https://github.com/mperham/connection_pool" rel="noreferrer">https://github.com/mperham/connection_pool</a>) so you have maximum
    20-30 concurrent requests going (this maximum number should be
    determined by testing at which point network performance drops and you
    get these timeouts)</p>
  </blockquote>
  
  <p>So for this part, I turned to <code>concurrent-ruby</code> and implemented both a <code>CachedThreadPool</code> and a <code>FixedThreadPool</code> with10 threads. I chose a `CachedThreadPool because it seemed to me that the number of threads needed would be taken care of for me by the Threadpool. Now in concurrent ruby's documentation for a pool, I see this:</p>
  
  <pre><code>pool = Concurrent::CachedThreadPool.new
  pool.post do
    # some parallel work
  end
  </code></pre>
  
  <p>I thought we just established in the first article that parallelism is not supported in Ruby, so what is the thread pool doing? Is it working concurrently or in parallel? What exactly is going on? Do I need a thread pool or not? Also at this point in time I thought connection pools and thread pools were the same just used interchangeably. What is the difference between the two pools and which one do I need?</p>
  
  <p>In another excellent article <a href="https://pawelurbanek.com/ruby-concurrent-requests" rel="noreferrer">How to Perform Concurrent HTTP Requests in Ruby and Rails</a>, this article introduces the <code>Concurrent::Promises</code> class form concurrent ruby to avoid locks and have thread safety with two api calls. Here is a snippet of code below with the following description:</p>
  
  <pre><code>def get_all_conversations
    groups_thread = Thread.new do
      get_groups_list
    end
  
    channels_thread = Thread.new do
      get_channels_list
    end
  
    [groups_thread, channels_thread].map(&amp;:value).flatten
  end
  </code></pre>
  
  <blockquote>
    <p>Every request is executed it its own thread, which can run in parallel because it is a blocking I/O. But can you see a catch here?</p>
  </blockquote>
  
  <p>In the above code, another mention of parallelism which we just said didn't exist in ruby. Below is the approach with <code>Concurrent::Promise</code></p>
  
  <pre><code>def get_all_conversations
    groups_promise = Concurrent::Promise.execute do
      get_groups_list
    end
  
    channels_promise = Concurrent::Promise.execute do
      get_channels_list
    end
  
    [groups_promise, channels_promise].map(&amp;:value!).flatten
  end
  </code></pre>
  
  <p>So according to this article, these requests are being made 'in parallel'. Are we still talking about concurrency at this point?</p>
  
  <p>Finally, in these two articles, they talk about using <code>Futures</code> for concurrent http requests. I won't go into the details but I'll paste the links here.</p>
  
  <p>1.<a href="https://blog.bigbinary.com/2018/06/05/using-concurrent-ruby-in-a-ruby-on-rails-application.html" rel="noreferrer">Using Concurrent Ruby in a Ruby on Rails Application</a>
  2. <a href="https://www.sitepoint.com/learn-concurrency-by-implementing-futures-in-ruby/" rel="noreferrer">Learn Concurrency by Implementing Futures in Ruby</a></p>
  
  <p>Again, what's talked about in the article looks to me like the <code>Concurrent::Promise</code> functionality. I just want to note that the examples show how to use the concepts for two different API calls that need to be combined together. This is not what I need. I just need to make thousands of API calls fast and log the results.</p>
  
  <p>In conclusion, I just want to know what I need to do to make my code faster and thread safe to make it run concurrently. What exactly am I missing to make the code go faster because right now it is going so slow that I might as well not have used threads in the first place.</p>
  
  <h2>Summary</h2>
  
  <p>I have to ping thousands of URLs using threads to speed up the process. The code is slow and I am confused if I am using threads, thread pools, and concurrency correctly.</p>