Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Change httpclient adapter to use thread local variables for thread safety #300

Merged
merged 1 commit into from

2 participants

@tbeauvais

This fixes a threading issue when doing load testing using webmock for our HttpClient service calls using JRuby. The httpclient_adapter was using shared instance variables which caused response corruption.

Oh I noticed before I even started that there were several spec failures. I looked it to it quickly and noticed that calls http://www.example.com/ now results in 200, with actual content. Is someone looking at that?

@bblimke
Owner

Cheers. One question:

Is there a reason for using non http client specific global variable Thread.current[:webmock_responses]
instead of instance variable @webmock_responses[Thread.current.object_id]

@tbeauvais

Since that instance variable is shared across threads it's not safe to have simultaneous writes to an Array or Hash. Adding to the hash is not an atomic operation so you could trash the hash even when using different keys.

Make sense?

@bblimke
Owner

I see. Ok that makes sense, cheers. It would be nice to make it at least in part http client specific. Thread.current[:webmock_responses] is very global. It could be as well used by other adapters.

@tbeauvais

ok, updated...

@bblimke bblimke merged commit 04d5fe1 into from
@bblimke
Owner

I think I have merged it too early. It seems to break http client support in ruby 1.8.7

@tbeauvais

Ruby 1.8.7, what's that :-) seriously isn't that deprecated? I guess if you really want to still support 1.8.7 you can back out my changes, then when I get a chance I can look into it.

@bblimke
Owner

whether I like it or not 1.8.7 is still supported by WebMock 1.x :)

@bblimke bblimke referenced this pull request from a commit
@bblimke Revert "Merge pull request #300 from tbeauvais/httpclient_thread_safe"
This reverts commit 04d5fe1, reversing
changes made to be0df72.
20d49b0
@bblimke
Owner

I reverted it. It breaks specs in async mode on Ruby 1.8.7. No idea why yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 16, 2013
This page is out of date. Refresh to see the latest.
Showing with 17 additions and 4 deletions.
  1. +17 −4 lib/webmock/http_lib_adapters/httpclient_adapter.rb
View
21 lib/webmock/http_lib_adapters/httpclient_adapter.rb
@@ -26,6 +26,8 @@ def self.disable!
end
end
+ WEBMOCK_HTTPCLIENT_RESPONSES = :webmock_responses
+ WEBMOCK_HTTPCLIENT_REQUEST_SIGNATURES = :webmock_request_signatures
class WebMockHTTPClient < HTTPClient
alias_method :do_get_block_without_webmock, :do_get_block
@@ -40,6 +42,9 @@ def do_get_stream(req, proxy, conn, &block)
end
def do_get(req, proxy, conn, stream = false, &block)
+
+ clear_thread_variables unless conn.async_thread
+
request_signature = build_request_signature(req, :reuse_existing)
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -78,12 +83,15 @@ def do_get(req, proxy, conn, stream = false, &block)
end
def do_request_async(method, uri, query, body, extheader)
+ clear_thread_variables
req = create_request(method, uri, query, body, extheader)
request_signature = build_request_signature(req)
webmock_request_signatures << request_signature
-
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
- super
+ conn = super
+ conn.async_thread[WEBMOCK_HTTPCLIENT_REQUEST_SIGNATURES] = Thread.current[WEBMOCK_HTTPCLIENT_REQUEST_SIGNATURES]
+ conn.async_thread[WEBMOCK_HTTPCLIENT_RESPONSES] = Thread.current[WEBMOCK_HTTPCLIENT_RESPONSES]
+ conn
else
raise WebMock::NetConnectNotAllowedError.new(request_signature)
end
@@ -172,13 +180,13 @@ def build_request_signature(req, reuse_existing = false)
end
def webmock_responses
- @webmock_responses ||= Hash.new do |hash, request_signature|
+ Thread.current[WEBMOCK_HTTPCLIENT_RESPONSES] ||= Hash.new do |hash, request_signature|
hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
end
end
def webmock_request_signatures
- @webmock_request_signatures ||= []
+ Thread.current[WEBMOCK_HTTPCLIENT_REQUEST_SIGNATURES] ||= []
end
def previous_signature_for(signature)
@@ -186,4 +194,9 @@ def previous_signature_for(signature)
webmock_request_signatures.delete_at(index)
end
+ def clear_thread_variables
+ Thread.current[WEBMOCK_HTTPCLIENT_REQUEST_SIGNATURES] = nil
+ Thread.current[WEBMOCK_HTTPCLIENT_RESPONSES] = nil
+ end
+
end
Something went wrong with that request. Please try again.