git ssb

0+

dangerousbeans / %aPBe2k3ugtjBr4rrsU1…



Commit b4024d4b1505f3c87b4c0b1ed59b8850aeea719f

Ruby 1.9: do not unlock the GIL unless the bcrypt cost is sufficiently high.

Locking/unlocking the GIL incurs some overhead as well so we want to avoid
that for cheap operations.

(merged with JRuby changes)

Signed-off-by: Coda Hale <coda.hale@gmail.com>
Hongli Lai (Phusion) authored on 8/5/2009, 7:32:55 AM
Coda Hale committed on 8/12/2009, 10:09:46 PM
Parent: 8caa1cce2f45c862ce127c2cbdeea0490b55d57f

Files changed

ext/bcrypt_ext.cchanged
lib/bcrypt.rbchanged
spec/bcrypt/engine_spec.rbchanged
ext/bcrypt_ext.cView
@@ -16,10 +16,12 @@
1616
1717 #ifdef RUBY_1_9
1818
1919 /* When on Ruby 1.9+, we will want to unlock the GIL while performing
20- * expensive calculations, for greater concurrency.
20+ * expensive calculations, for greater concurrency. Do not do this for
21+ * cheap calculations because locking/unlocking the GIL incurs some overhead as well.
2122 */
23+ #define GIL_UNLOCK_COST_THRESHOLD 9
2224
2325 typedef struct {
2426 char *output;
2527 const char *key;
@@ -44,38 +46,42 @@
4446 }
4547
4648 /* Given a secret and a salt, generates a salted hash (which you can then store safely).
4749 */
48-static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt) {
50+static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt, VALUE cost) {
4951 const char * safeguarded = RSTRING_PTR(key) ? RSTRING_PTR(key) : "";
5052 char output[BCRYPT_OUTPUT_SIZE];
5153
5254 #ifdef RUBY_1_9
53- BCryptArguments args;
54- VALUE ret;
55+ int icost = NUM2INT(cost);
56+ if (icost >= GIL_UNLOCK_COST_THRESHOLD) {
57+ BCryptArguments args;
58+ VALUE ret;
5559
56- args.output = output;
57- args.key = safeguarded;
58- args.salt = RSTRING_PTR(salt);
59- ret = rb_thread_blocking_region(bcrypt_wrapper, &args, RUBY_UBF_IO, 0);
60- if (ret != (VALUE) 0) {
61- return rb_str_new2(output);
62- } else {
63- return Qnil;
60+ args.output = output;
61+ args.key = safeguarded;
62+ args.salt = RSTRING_PTR(salt);
63+ ret = rb_thread_blocking_region(bcrypt_wrapper, &args, RUBY_UBF_IO, 0);
64+ if (ret != (VALUE) 0) {
65+ return rb_str_new2(output);
66+ } else {
67+ return Qnil;
68+ }
6469 }
65- #else
66- if (bcrypt(output, safeguarded, (char *)RSTRING_PTR(salt)) != NULL) {
67- return rb_str_new2(output);
68- } else {
69- return Qnil;
70- }
70+ /* otherwise, fallback to the non-GIL-unlocking code, just like on Ruby 1.8 */
7171 #endif
72+
73+ if (bcrypt(output, safeguarded, (char *)RSTRING_PTR(salt)) != NULL) {
74+ return rb_str_new2(output);
75+ } else {
76+ return Qnil;
77+ }
7278 }
7379
7480 /* Create the BCrypt and BCrypt::Engine modules, and populate them with methods. */
7581 void Init_bcrypt_ext(){
7682 mBCrypt = rb_define_module("BCrypt");
7783 cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject);
7884
7985 rb_define_singleton_method(cBCryptEngine, "__bc_salt", bc_salt, 2);
80- rb_define_singleton_method(cBCryptEngine, "__bc_crypt", bc_crypt, 2);
86+ rb_define_singleton_method(cBCryptEngine, "__bc_crypt", bc_crypt, 3);
8187 }
lib/bcrypt.rbView
@@ -37,15 +37,19 @@
3737 end
3838
3939 # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
4040 # a bcrypt() password hash.
41- def self.hash_secret(secret, salt)
41+ def self.hash_secret(secret, salt, cost = nil)
4242 if valid_secret?(secret)
4343 if valid_salt?(salt)
44+ if cost.nil?
45+ cost = autodetect_cost(salt)
46+ end
47+
4448 if RUBY_PLATFORM == "java"
4549 Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
4650 else
47- __bc_crypt(secret.to_s, salt)
51+ __bc_crypt(secret.to_s, salt, cost)
4852 end
4953 else
5054 raise Errors::InvalidSalt.new("invalid salt")
5155 end
@@ -100,8 +104,13 @@
100104 end_time = Time.now - start_time
101105 return i if end_time * 1_000 > upper_time_limit_in_ms
102106 end
103107 end
108+
109+ # Autodetects the cost from the salt string.
110+ def self.autodetect_cost(salt)
111+ salt[4..5].to_i
112+ end
104113 end
105114
106115 # A password management class which allows you to safely store users' passwords and compare them.
107116 #
@@ -144,9 +153,9 @@
144153 # Example:
145154 #
146155 # @password = BCrypt::Password.create("my secret", :cost => 13)
147156 def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
148- Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost])))
157+ Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost]))
149158 end
150159 end
151160
152161 # Initializes a BCrypt::Password instance with the data from a stored hash.
spec/bcrypt/engine_spec.rbView
@@ -26,8 +26,18 @@
2626 lambda { BCrypt::Engine.generate_salt(-1) }.should raise_error(BCrypt::Errors::InvalidCost)
2727 end
2828 end
2929
30+context "Autodetecting of salt cost" do
31+
32+ specify "should work" do
33+ BCrypt::Engine.autodetect_cost("$2a$08$hRx2IVeHNsTSYYtUWn61Ou").should == 8
34+ BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu").should == 5
35+ BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.").should == 13
36+ end
37+
38+end
39+
3040 context "Generating BCrypt hashes" do
3141
3242 class MyInvalidSecret
3343 undef to_s

Built with git-ssb-web