{"action":"created","issue":{"url":"https://api.github.com/repos/net-ssh/net-ssh/issues/240","labels_url":"https://api.github.com/repos/net-ssh/net-ssh/issues/240/labels{/name}","comments_url":"https://api.github.com/repos/net-ssh/net-ssh/issues/240/comments","events_url":"https://api.github.com/repos/net-ssh/net-ssh/issues/240/events","html_url":"https://github.com/net-ssh/net-ssh/pull/240","id":76166814,"number":240,"title":"Fix NoMethodError `recv` in multithreaded usage of Net::SSH::Proxy::Command","user":{"login":"mattbrictson","id":189693,"avatar_url":"https://avatars.githubusercontent.com/u/189693?v=3","gravatar_id":"","url":"https://api.github.com/users/mattbrictson","html_url":"https://github.com/mattbrictson","followers_url":"https://api.github.com/users/mattbrictson/followers","following_url":"https://api.github.com/users/mattbrictson/following{/other_user}","gists_url":"https://api.github.com/users/mattbrictson/gists{/gist_id}","starred_url":"https://api.github.com/users/mattbrictson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mattbrictson/subscriptions","organizations_url":"https://api.github.com/users/mattbrictson/orgs","repos_url":"https://api.github.com/users/mattbrictson/repos","events_url":"https://api.github.com/users/mattbrictson/events{/privacy}","received_events_url":"https://api.github.com/users/mattbrictson/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2015-05-13T23:54:21Z","updated_at":"2015-05-14T05:10:26Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/net-ssh/net-ssh/pulls/240","html_url":"https://github.com/net-ssh/net-ssh/pull/240","diff_url":"https://github.com/net-ssh/net-ssh/pull/240.diff","patch_url":"https://github.com/net-ssh/net-ssh/pull/240.patch"},"body":"This fixes a longstanding bug discovered by users of SSHKit: https://github.com/capistrano/sshkit/issues/150.\r\n\r\nTo summarize: when SSH connections are made in multiple threads using `ProxyCommand`, there is a small possibility that `Net::SSH.start` will fail with an error like this:\r\n\r\n```\r\nNoMethodError: undefined method `recv' for #<IO:fd 47>\r\n```\r\n\r\nI traced the problem down to Ruby itself. Apparently, defining singleton methods using this syntax (as is done by `proxy/command.rb`) is not thread safe:\r\n\r\n```ruby\r\nclass << io\r\n  def send(data, flag)\r\n    write_nonblock(data)\r\n  end\r\n\r\n  def recv(size)\r\n    read_nonblock(size)\r\n  end\r\nend\r\n```\r\n\r\nWhen executed in parallel by many threads, there is a small chance that the `recv` method will not be defined. As strange as this bug may seem, you can reproduce it consistently with this test case:\r\n\r\n```ruby\r\nrequire \"thread\"\r\n\r\ndef define_singleton_methods_in_thread\r\n  Thread.new do\r\n    100_000.times do\r\n      obj = Object.new\r\n      class << obj\r\n        def one\r\n        end\r\n        def two\r\n        end\r\n        def three\r\n        end\r\n      end\r\n      puts \"FAIL\" unless obj.respond_to?(:three)\r\n    end\r\n  end\r\nend\r\n\r\n# Test with 2 threads\r\n2.times.map { define_singleton_methods_in_thread }.map(&:join)\r\n```\r\n\r\nOn my machine this outputs `FAIL` three times.\r\n\r\nThe good news is that there is another way to define singleton methods in Ruby that *is* thread safe:\r\n\r\n```ruby\r\n# Not thread safe :(\r\nclass << obj\r\n  def one\r\n  end\r\n  def two\r\n  end\r\n  def three\r\n  end\r\nend\r\n\r\n# Thread safe :)\r\ndef obj.one\r\nend\r\ndef obj.two\r\nend\r\ndef obj.three\r\nend\r\n```\r\n\r\nThis PR rewrites changes the way `send` and `recv` are declared to use the latter syntax.\r\n\r\nI plan to also open a bug report for MRI, but for now this should work around the problem."},"comment":{"url":"https://api.github.com/repos/net-ssh/net-ssh/issues/comments/101915127","html_url":"https://github.com/net-ssh/net-ssh/pull/240#issuecomment-101915127","issue_url":"https://api.github.com/repos/net-ssh/net-ssh/issues/240","id":101915127,"user":{"login":"mfazekas","id":52435,"avatar_url":"https://avatars.githubusercontent.com/u/52435?v=3","gravatar_id":"","url":"https://api.github.com/users/mfazekas","html_url":"https://github.com/mfazekas","followers_url":"https://api.github.com/users/mfazekas/followers","following_url":"https://api.github.com/users/mfazekas/following{/other_user}","gists_url":"https://api.github.com/users/mfazekas/gists{/gist_id}","starred_url":"https://api.github.com/users/mfazekas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mfazekas/subscriptions","organizations_url":"https://api.github.com/users/mfazekas/orgs","repos_url":"https://api.github.com/users/mfazekas/repos","events_url":"https://api.github.com/users/mfazekas/events{/privacy}","received_events_url":"https://api.github.com/users/mfazekas/received_events","type":"User","site_admin":false},"created_at":"2015-05-14T05:10:25Z","updated_at":"2015-05-14T05:10:25Z","body":"Thanks for the report, strange bug indeed."}}