Skip to content

Commit

Permalink
Fix concurrency issues
Browse files Browse the repository at this point in the history
Fixes ArrayIndexOutOfBoundsException that can occur when serializing dates concurrently.
Also cache the value of "UTC" TimeZone locally to avoid contention to synchronized
`getTimeZone` method.

Fixes guyboertje#76, guyboertje#77
  • Loading branch information
robbavey committed Sep 23, 2019
1 parent cfa003c commit c8bc91a
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ end

desc "Run benchmarks"
task :benchmark do
load 'benchmarking/benchmark.rb'
load 'benchmarking/benchmark_threaded.rb'
end

desc "Pack jar after compiling classes, use this to rebuild the pom.xml"
Expand Down
50 changes: 44 additions & 6 deletions benchmarking/benchmark_threaded.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require 'json/ext'
require 'gson'

require File.expand_path('lib/jrjackson_jars')
require File.expand_path('lib/jrjackson')

HASH = {:one => nil, :two => nil, :three => nil, :four => {:a => nil, :b => nil, :c =>nil},
Expand All @@ -24,6 +25,10 @@ def random_number
rand 999_999_999
end

def random_date
Time.at(rand * Time.now.to_i)
end

def random_float
random_number + rand
end
Expand All @@ -36,40 +41,73 @@ def fill_array
value
end

def randomize_entries hsh
def randomize_entries hsh, include_dates
new_hsh = {}
hsh.each_pair do |key, value|
case value
when NilClass
new_hsh[key] = send METHODS[rand(3)]
new_hsh[key] = send METHODS[rand(include_dates ? 4 : 3)]
when Hash
new_hsh[key] = randomize_entries value
new_hsh[key] = randomize_entries value, include_dates
when Array
new_hsh[key] = fill_array
end
end
new_hsh
end

METHODS = [:random_string, :random_number, :random_float]
METHODS = [:random_string, :random_number, :random_float, :random_date]

org_array = []
no_dates_array = []
one = []

0.upto(5000) do |i|
hsh = HASH.dup
org_array << randomize_entries(hsh)
org_array << randomize_entries(hsh, true)
end

0.upto(5000) do |i|
hsh = HASH.dup
no_dates_array << randomize_entries(hsh, false)
end


generated_array = []
generated_smile = []

org_array.each do |hsh|
generated_array << JrJackson::Raw.generate(hsh)
generated_array << JrJackson::Base.generate(hsh)
end

q = Queue.new


Benchmark.bmbm("Jackson generate with no dates".size) do |x|
x.report("Jackson generate with dates") do
threads = 100.times.map do
Thread.new do
org_array.each do |hsh|
JrJackson::Base.generate(hsh)
end
end
end
threads.each(&:join)
end

x.report("Jackson generate with no dates") do

threads = 100.times.map do
Thread.new do
no_dates_array.each do |hsh|
JrJackson::Base.generate(hsh)
end
end
end
threads.each(&:join)
end
end

Benchmark.bmbm("jackson parse symbol keys: ".size) do |x|

x.report("json java parse:") do
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v0.4.10
fix concurrency issue when serializing dates. Also cache the value of "UTC" TimeZone locally to
avoid contention to synchronized `getTimeZone` method.

v0.4.9
bump Jackson to v2.9.9, and jackson-databind to v2.9.9.3

Expand Down
4 changes: 2 additions & 2 deletions lib/jrjackson/build_info.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module JrJackson
module BuildInfo
def self.version
'0.4.9'
'0.4.10'
end

def self.release_date
'2019-08-09'
'2019-09-24'
end

def self.files
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/jrjackson/RubyAnySerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,11 @@ private void serializeTime(RubyTime dt, JsonGenerator jgen, SerializerProvider p
} else if (df instanceof RubyDateFormat) {
// why another branch? I thought there was an easy win on to_s
// maybe with jruby 9000
RubyDateFormat rdf = (RubyDateFormat) df.clone();
jgen.writeString(rdf.format(dt.getJavaDate()));
RubyDateFormat clonedRubyDatFormat = (RubyDateFormat) df.clone();
jgen.writeString(clonedRubyDatFormat.format(dt.getJavaDate()));
} else {
SimpleDateFormat sdf = (SimpleDateFormat) df.clone();
jgen.writeString(df.format(dt.getJavaDate()));
SimpleDateFormat clonedSimpleDateFormat = (SimpleDateFormat) df.clone();
jgen.writeString(clonedSimpleDateFormat.format(dt.getJavaDate()));
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/jrjackson/RubyJacksonModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
public class RubyJacksonModule extends SimpleModule {
public static final ObjectMapper static_mapper = new ObjectMapper();
public static final JsonFactory factory = new JsonFactory(static_mapper).disable(JsonFactory.Feature.FAIL_ON_SYMBOL_HASH_OVERFLOW);
private static final TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");

static {
static_mapper.registerModule(new RubyJacksonModule().addSerializer(
Expand Down Expand Up @@ -55,7 +56,7 @@ public static DefaultSerializerProvider createProvider(SimpleDateFormat sdf) {

public static DefaultSerializerProvider createProvider() {
SimpleDateFormat rdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
rdf.setTimeZone(TimeZone.getTimeZone("UTC"));
rdf.setTimeZone(utcTimeZone);
return createProvider(rdf);
}

Expand Down
15 changes: 15 additions & 0 deletions test/jrjackson_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,21 @@ def test_can_handle_big_numbers
assert_equal "{\"foo\":9223372036854775808,\"bar\":65536}", actual
end


# This test failed more often than not before fixing the underlying code
# and would fail every time if `100_000.times` was changed to `loop`
def test_concurrent_dump
now = Time.now
num_threads = 100

threads = num_threads.times.map do |i|
Thread.new do
100_000.times { JrJackson::Json.dump("a" => now) }
end
end
threads.each(&:join)
end

# -----------------------------

def assert_bigdecimal_equal(expected, actual)
Expand Down

0 comments on commit c8bc91a

Please sign in to comment.