diff --git a/Rakefile b/Rakefile index c545c2f..44425c8 100644 --- a/Rakefile +++ b/Rakefile @@ -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" diff --git a/benchmarking/benchmark_threaded.rb b/benchmarking/benchmark_threaded.rb index 60cc310..0ea3df3 100644 --- a/benchmarking/benchmark_threaded.rb +++ b/benchmarking/benchmark_threaded.rb @@ -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}, @@ -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 @@ -36,14 +41,14 @@ 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 @@ -51,25 +56,58 @@ def randomize_entries hsh 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 diff --git a/changelog.md b/changelog.md index e81e79f..6a53d0a 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/lib/jrjackson/build_info.rb b/lib/jrjackson/build_info.rb index 4128517..a822ad5 100644 --- a/lib/jrjackson/build_info.rb +++ b/lib/jrjackson/build_info.rb @@ -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 diff --git a/src/main/java/com/jrjackson/RubyAnySerializer.java b/src/main/java/com/jrjackson/RubyAnySerializer.java index 02c3e43..8e3370d 100644 --- a/src/main/java/com/jrjackson/RubyAnySerializer.java +++ b/src/main/java/com/jrjackson/RubyAnySerializer.java @@ -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())); } } diff --git a/src/main/java/com/jrjackson/RubyJacksonModule.java b/src/main/java/com/jrjackson/RubyJacksonModule.java index 4f1ff02..6c21f0a 100644 --- a/src/main/java/com/jrjackson/RubyJacksonModule.java +++ b/src/main/java/com/jrjackson/RubyJacksonModule.java @@ -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( @@ -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); } diff --git a/test/jrjackson_test.rb b/test/jrjackson_test.rb index 2d73b08..6aa3e72 100755 --- a/test/jrjackson_test.rb +++ b/test/jrjackson_test.rb @@ -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)