From 3a2ac8282aef674f798dcef1909f569c78ca1a4b Mon Sep 17 00:00:00 2001
From: Eric Wong <e@80x24.org>
Date: Fri, 12 May 2017 20:19:41 +0000
Subject: [PATCH] autoload: always wait on loading thread

We cannot assume autoload_provided/rb_feature_provided returning
TRUE means it is safe to proceed without waiting.  Another
thread may call rb_provide_feature before setting the constant
(via autoload_const_set).  So we must wait until autoload is
completed by another thread.

Note: this patch was tested with an explicit rb_thread_schedule
in rb_provide_feature to make the race condition more apparent
as suggested by <s.wanabe@gmail.com>:
> --- a/load.c
> +++ b/load.c
> @@ -563,6 +563,7 @@ rb_provide_feature(VALUE feature)
>      rb_str_freeze(feature);
>
>      rb_ary_push(features, rb_fstring(feature));
> +rb_thread_schedule();
>      features_index_add(feature, INT2FIX(RARRAY_LEN(features)-1));
>      reset_loaded_features_snapshot();
>  }

* variable.c (check_autoload_required): do not assume a provided
  feature means autoload is complete, always wait if autoload is
  being performed by another thread.
  [ruby-core:81105] [Bug #11384] Thanks to <s.wanabe@gmail.com>
---
 variable.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/variable.c b/variable.c
index 6e883c7041..63c5f2aeab 100644
--- a/variable.c
+++ b/variable.c
@@ -1987,6 +1987,17 @@ check_autoload_required(VALUE mod, ID id, const char **loadingpath)
     if (!RSTRING_LEN(file) || !*RSTRING_PTR(file)) {
 	rb_raise(rb_eArgError, "empty file name");
     }
+
+    /*
+     * if somebody else is autoloading, we MUST wait for them, since
+     * rb_provide_feature can provide a feature before autoload_const_set
+     * completes.  We must wait until autoload_const_set finishes in
+     * the other thread.
+     */
+    if (ele->state && ele->state->thread != rb_thread_current()) {
+	return load;
+    }
+
     loading = RSTRING_PTR(file);
     safe = rb_safe_level();
     rb_set_safe_level_force(0);
-- 
EW

