-
Type: Bug
-
Resolution: Duplicate
-
Priority: Major - P3
-
None
-
Affects Version/s: None
-
Component/s: BSON
-
None
-
Ruby Drivers
As I started testing BSON 5.0 in my ruby codebase I noticed some failing tests using far-future dates (that I introduced on purpuse in my codebase to detect when libraries accidentally breaks supports for them, good thing I did ☺). I noticed one such problem with BSON 5.0 which now raises an exception when we try to generate a BSON::ObjectID after 2038 (the famous signed int timestamp limit)
I know the BSON specs only supports 4 bytes so limits to year 2106 when using unsigned int. After that it's supposed to wrap around back to 0 (1970) I suppose, which is not perfect but good enough for the 70 coming years IMO
But here the problem is much worse, not only it doesn't wrap around when using ObjectId.new (it raises instead), but also there must be a signed int somewhere because it fails at 2038 instead of 2106.
The problem was instroduced in https://github.com/mongodb/bson-ruby/commit/9484d2a0caa1414c4ec1e8cfb6cddf42e6e71cd4 for https://github.com/mongodb/bson-ruby/pull/311 in my opinion.
I tried to write the smallest reproducible example possible (`bson-5.0-2038bug.rb`):
#!/usr/bin/env ruby require 'bundler/inline' # example: # ./bson-5.0-2038bug.rb 2039 gemfile do source 'https://rubygems.org' gem 'bson', '5.0.0' gem 'activesupport', '7.0.8' end year = (ARGV.first || 2222).to_i puts "BSON: #{BSON::VERSION}" puts "YEAR: #{year}" # stub Time.now require 'active_support/testing/time_helpers' include ActiveSupport::Testing::TimeHelpers travel_to(Time.utc(year, 1, 1)) puts "Time.now: #{Time.now} (#{Time.now.to_i})" puts "\nBSON::ObjectId.from_time(Time.now) =>" id = BSON::ObjectId.from_time(Time.now) puts " #{id.inspect} (time: #{id.generation_time})" puts "\nBSON::ObjectId.new =>" id = BSON::ObjectId.new puts " #{id.inspect} (time: #{id.generation_time})"
Here are some results with BSON 5.0:
# ./bson-5.0-2038bug.rb 2038 BSON: 5.0.0 YEAR: 2038 Time.now: 2038-01-01 01:00:00 +0100 (2145916800) BSON::ObjectId.from_time(Time.now) => BSON::ObjectId('7fe817800000000000000000') (time: 2038-01-01 00:00:00 UTC) BSON::ObjectId.new => BSON::ObjectId('7fe817802dba73a68165bc96') (time: 2038-01-01 00:00:00 UTC) # ./bson-5.0-2038bug.rb 2039 BSON: 5.0.0 YEAR: 2039 Time.now: 2039-01-01 01:00:00 +0100 (2177452800) BSON::ObjectId.from_time(Time.now) => BSON::ObjectId('81c94b000000000000000000') (time: 2039-01-01 00:00:00 UTC) BSON::ObjectId.new => /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:246:in `next_object_id': integer 2177452800 too big to convert to `int' (RangeError) from /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:246:in `generate_data' from /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:192:in `to_s' from /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:135:in `inspect' from ../bson-5.0-2038bug.rb:31:in `<main>' # ./bson-5.0-2038bug.rb 2107 BSON: 5.0.0 YEAR: 2107 Time.now: 2107-01-01 01:00:00 +0100 (4323283200) BSON::ObjectId.from_time(Time.now) => BSON::ObjectId('01b011000000000000000000') (time: 1970-11-24 17:31:44 UTC) BSON::ObjectId.new => /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:246:in `next_object_id': integer 4323283200 too big to convert to `int' (RangeError) from /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:246:in `generate_data' from /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:192:in `to_s' from /home/bigbourin/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bson-5.0.0/lib/bson/object_id.rb:135:in `inspect' from ../bson-5.0-2038bug.rb:31:in `<main>'
We can see that:
- before 2037 all is well
- after 2037 `ObjectId.new` starts failing due the signed int problem (I suppose because you're using `NUM2INT` instead of `NUM2UINT`). We can see `from_time` is still working though, because it's not using the same C code.
- after 2106 we can see `from_time` correctly wraps around the integer overflow and goes back to 1970, while ObjectId.new is still broken.
So the problem is twofold IMO:
- Should use `NUM2UINT` to support properly times up to 2106
- After that `NUM2UINT` will also fail so I guess you should add a modulo before the UINT conversion or something like that, so it wraps around without raising.
Just to be clear, in BSON 4.15 I didn't have the issue but that's only because the C code was not calling to ruby's Time.now so my time stubbing in specs was not taken into account. It's harder to stub the C time() function used in BSON 4.15 so I didn't try but from what I read in the C code, BSON 4.15 doesn't have this bug (that's why I'm calling it a regression) and should work at least until 2106:
# ./bson-5.0-2038bug.rb 2107 BSON: 4.15.0 YEAR: 2107 Time.now: 2107-01-01 01:00:00 +0100 (4323283200) BSON::ObjectId.from_time(Time.now) => BSON::ObjectId('01b011000000000000000000') (time: 1970-11-24 17:31:44 UTC) BSON::ObjectId.new => BSON::ObjectId('661550fbb2c79a93f3d1f59c') (time: 2024-04-09 14:30:19 UTC)
ps: it would be nice to have a link on the github readme for BSON about where to post issues. I know because I've used your jira hundreds of times unfortunately, but for other users, it's pretty cumbersome to find with the issues tab disabled on github and no link in the readme.
Thanks!
- duplicates
-
RUBY-3435 RangeError: bignum too big to convert into `long'
- Closed