-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Double-checked locking bugs #6662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e36b84b
fb5eaa8
14d9689
0814d5b
abbfab5
9641323
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,27 +21,43 @@ | |
|
|
||
| import com.amazonaws.auth.AWSCredentials; | ||
| import com.amazonaws.auth.AWSCredentialsProvider; | ||
| import org.checkerframework.checker.nullness.qual.EnsuresNonNull; | ||
| import org.checkerframework.checker.nullness.qual.MonotonicNonNull; | ||
|
|
||
| public class LazyFileSessionCredentialsProvider implements AWSCredentialsProvider | ||
| { | ||
| private AWSCredentialsConfig config; | ||
| private FileSessionCredentialsProvider provider; | ||
| private final AWSCredentialsConfig config; | ||
|
|
||
| /** | ||
| * The field is declared volatile in order to ensure safe publication of the object | ||
| * in {@link #getUnderlyingProvider()} without worrying about final modifiers | ||
| * on the fields of the created object | ||
| * | ||
| * @see <a href="https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157"> | ||
| * https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157</a> | ||
| */ | ||
| @MonotonicNonNull | ||
| private volatile FileSessionCredentialsProvider provider; | ||
|
|
||
| public LazyFileSessionCredentialsProvider(AWSCredentialsConfig config) | ||
| { | ||
| this.config = config; | ||
| } | ||
|
|
||
| @EnsuresNonNull("provider") | ||
| private FileSessionCredentialsProvider getUnderlyingProvider() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| { | ||
| if (provider == null) { | ||
| FileSessionCredentialsProvider syncedProvider = provider; | ||
| if (syncedProvider == null) { | ||
| synchronized (config) { | ||
| if (provider == null) { | ||
| provider = new FileSessionCredentialsProvider(config.getFileSessionCredentials()); | ||
| syncedProvider = provider; | ||
| if (syncedProvider == null) { | ||
| syncedProvider = new FileSessionCredentialsProvider(config.getFileSessionCredentials()); | ||
| provider = syncedProvider; | ||
| } | ||
| } | ||
| } | ||
| return provider; | ||
| return syncedProvider; | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,8 @@ | |
| import org.apache.druid.segment.BaseObjectColumnValueSelector; | ||
| import org.apache.druid.segment.ColumnSelectorFactory; | ||
| import org.apache.druid.segment.ColumnValueSelector; | ||
| import org.checkerframework.checker.nullness.qual.EnsuresNonNull; | ||
| import org.checkerframework.checker.nullness.qual.MonotonicNonNull; | ||
| import org.mozilla.javascript.Context; | ||
| import org.mozilla.javascript.ContextAction; | ||
| import org.mozilla.javascript.ContextFactory; | ||
|
|
@@ -58,8 +60,15 @@ public class JavaScriptAggregatorFactory extends AggregatorFactory | |
| private final String fnCombine; | ||
| private final JavaScriptConfig config; | ||
|
|
||
| // This variable is lazily initialized to avoid unnecessary JavaScript compilation during JSON serde | ||
| private JavaScriptAggregator.ScriptAggregator compiledScript; | ||
| /** | ||
| * The field is declared volatile in order to ensure safe publication of the object | ||
| * in {@link #compileScript(String, String, String)} without worrying about final modifiers | ||
| * on the fields of the created object | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| * | ||
| * @see <a href="https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157"> | ||
| * https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157</a> | ||
| */ | ||
| private volatile JavaScriptAggregator.@MonotonicNonNull ScriptAggregator compiledScript; | ||
|
|
||
| @JsonCreator | ||
| public JavaScriptAggregatorFactory( | ||
|
|
@@ -89,7 +98,7 @@ public JavaScriptAggregatorFactory( | |
| @Override | ||
| public Aggregator factorize(final ColumnSelectorFactory columnFactory) | ||
| { | ||
| checkAndCompileScript(); | ||
| JavaScriptAggregator.ScriptAggregator compiledScript = getCompiledScript(); | ||
| return new JavaScriptAggregator( | ||
| fieldNames.stream().map(columnFactory::makeColumnValueSelector).collect(Collectors.toList()), | ||
| compiledScript | ||
|
|
@@ -99,7 +108,7 @@ public Aggregator factorize(final ColumnSelectorFactory columnFactory) | |
| @Override | ||
| public BufferAggregator factorizeBuffered(final ColumnSelectorFactory columnSelectorFactory) | ||
| { | ||
| checkAndCompileScript(); | ||
| JavaScriptAggregator.ScriptAggregator compiledScript = getCompiledScript(); | ||
| return new JavaScriptBufferAggregator( | ||
| fieldNames.stream().map(columnSelectorFactory::makeColumnValueSelector).collect(Collectors.toList()), | ||
| compiledScript | ||
|
|
@@ -115,7 +124,7 @@ public Comparator getComparator() | |
| @Override | ||
| public Object combine(Object lhs, Object rhs) | ||
| { | ||
| checkAndCompileScript(); | ||
| JavaScriptAggregator.ScriptAggregator compiledScript = getCompiledScript(); | ||
| return compiledScript.combine(((Number) lhs).doubleValue(), ((Number) rhs).doubleValue()); | ||
| } | ||
|
|
||
|
|
@@ -135,7 +144,7 @@ public void reset(ColumnValueSelector selector) | |
| @Override | ||
| public void fold(ColumnValueSelector selector) | ||
| { | ||
| checkAndCompileScript(); | ||
| JavaScriptAggregator.ScriptAggregator compiledScript = getCompiledScript(); | ||
| combined = compiledScript.combine(combined, selector.getDouble()); | ||
| } | ||
|
|
||
|
|
@@ -283,19 +292,24 @@ public String toString() | |
| * This class can be used by multiple threads, so this function should be thread-safe to avoid extra | ||
| * script compilation. | ||
| */ | ||
| private void checkAndCompileScript() | ||
| @EnsuresNonNull("compiledScript") | ||
| private JavaScriptAggregator.ScriptAggregator getCompiledScript() | ||
| { | ||
| if (compiledScript == null) { | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
|
|
||
| JavaScriptAggregator.ScriptAggregator syncedCompiledScript = compiledScript; | ||
| if (syncedCompiledScript == null) { | ||
| synchronized (config) { | ||
| if (compiledScript == null) { | ||
| compiledScript = compileScript(fnAggregate, fnReset, fnCombine); | ||
| syncedCompiledScript = compiledScript; | ||
| if (syncedCompiledScript == null) { | ||
| syncedCompiledScript = compileScript(fnAggregate, fnReset, fnCombine); | ||
| compiledScript = syncedCompiledScript; | ||
| } | ||
| } | ||
| } | ||
| return syncedCompiledScript; | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,8 @@ | |
| import org.apache.druid.query.aggregation.AggregatorFactory; | ||
| import org.apache.druid.query.aggregation.PostAggregator; | ||
| import org.apache.druid.query.cache.CacheKeyBuilder; | ||
| import org.checkerframework.checker.nullness.qual.EnsuresNonNull; | ||
| import org.checkerframework.checker.nullness.qual.MonotonicNonNull; | ||
| import org.mozilla.javascript.Context; | ||
| import org.mozilla.javascript.ContextFactory; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as in other classes |
||
| import org.mozilla.javascript.ScriptableObject; | ||
|
|
@@ -87,8 +89,16 @@ public double apply(Object[] args) | |
| private final String function; | ||
| private final JavaScriptConfig config; | ||
|
|
||
| // This variable is lazily initialized to avoid unnecessary JavaScript compilation during JSON serde | ||
| private Function fn; | ||
| /** | ||
| * The field is declared volatile in order to ensure safe publication of the object | ||
| * in {@link #compile(String)} without worrying about final modifiers | ||
| * on the fields of the created object | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| * | ||
| * @see <a href="https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157"> | ||
| * https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157</a> | ||
| */ | ||
| @MonotonicNonNull | ||
| private volatile Function fn; | ||
|
|
||
| @JsonCreator | ||
| public JavaScriptPostAggregator( | ||
|
|
@@ -123,7 +133,7 @@ public Comparator getComparator() | |
| @Override | ||
| public Object compute(Map<String, Object> combinedAggregators) | ||
| { | ||
| checkAndCompileScript(); | ||
| Function fn = getCompiledScript(); | ||
| final Object[] args = new Object[fieldNames.size()]; | ||
| int i = 0; | ||
| for (String field : fieldNames) { | ||
|
|
@@ -136,22 +146,24 @@ public Object compute(Map<String, Object> combinedAggregators) | |
| * {@link #compute} can be called by multiple threads, so this function should be thread-safe to avoid extra | ||
| * script compilation. | ||
| */ | ||
| private void checkAndCompileScript() | ||
| @EnsuresNonNull("fn") | ||
| private Function getCompiledScript() | ||
| { | ||
| if (fn == null) { | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
|
|
||
| // Synchronizing here can degrade the performance significantly because this method is called per input row. | ||
| // However, early compilation of JavaScript functions can occur some memory issues due to unnecessary compilation | ||
| // involving Java class generation each time, and thus this will be better. | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
|
|
||
| Function syncedFn = fn; | ||
| if (syncedFn == null) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @leventov Seems that here is always null at new case. Do I miss anything?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand you message.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems that first check for if (syncedFn == null)is unnecessary. On the other hand, I've just sent another commit to address your comments.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @leventov is everything OK for my PR? |
||
| synchronized (config) { | ||
| if (fn == null) { | ||
| fn = compile(function); | ||
| syncedFn = fn; | ||
| if (syncedFn == null) { | ||
| syncedFn = compile(function); | ||
| fn = syncedFn; | ||
| } | ||
| } | ||
| } | ||
| return syncedFn; | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,8 @@ | |
| import org.apache.druid.common.config.NullHandling; | ||
| import org.apache.druid.java.util.common.StringUtils; | ||
| import org.apache.druid.js.JavaScriptConfig; | ||
| import org.checkerframework.checker.nullness.qual.EnsuresNonNull; | ||
| import org.checkerframework.checker.nullness.qual.MonotonicNonNull; | ||
| import org.mozilla.javascript.Context; | ||
| import org.mozilla.javascript.ContextFactory; | ||
| import org.mozilla.javascript.ScriptableObject; | ||
|
|
@@ -69,8 +71,16 @@ public String apply(Object input) | |
| private final boolean injective; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the same comments as in the prev. classes |
||
| private final JavaScriptConfig config; | ||
|
|
||
| // This variable is lazily initialized to avoid unnecessary JavaScript compilation during JSON serde | ||
| private Function<Object, String> fn; | ||
| /** | ||
| * The field is declared volatile in order to ensure safe publication of the object | ||
| * in {@link #compile(String)} without worrying about final modifiers | ||
| * on the fields of the created object | ||
| * | ||
| * @see <a href="https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157"> | ||
| * https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157</a> | ||
| */ | ||
| @MonotonicNonNull | ||
| private volatile Function<Object, String> fn; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention |
||
|
|
||
| @JsonCreator | ||
| public JavaScriptExtractionFn( | ||
|
|
@@ -112,27 +122,32 @@ public byte[] getCacheKey() | |
| @Nullable | ||
| public String apply(@Nullable Object value) | ||
| { | ||
| checkAndCompileScript(); | ||
| Function<Object, String> fn = getCompiledScript(); | ||
| return NullHandling.emptyToNullIfNeeded(fn.apply(value)); | ||
| } | ||
|
|
||
| /** | ||
| * {@link #apply(Object)} can be called by multiple threads, so this function should be thread-safe to avoid extra | ||
| * script compilation. | ||
| */ | ||
| private void checkAndCompileScript() | ||
| @EnsuresNonNull("fn") | ||
| private Function<Object, String> getCompiledScript() | ||
| { | ||
| if (fn == null) { | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
|
|
||
| Function<Object, String> syncedFn = fn; | ||
| if (syncedFn == null) { | ||
| synchronized (config) { | ||
| if (fn == null) { | ||
| fn = compile(function); | ||
| syncedFn = fn; | ||
| if (syncedFn == null) { | ||
| syncedFn = compile(function); | ||
| fn = syncedFn; | ||
| } | ||
| } | ||
| } | ||
| return syncedFn; | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,8 @@ | |
| import org.apache.druid.js.JavaScriptConfig; | ||
| import org.apache.druid.query.extraction.ExtractionFn; | ||
| import org.apache.druid.segment.filter.JavaScriptFilter; | ||
| import org.checkerframework.checker.nullness.qual.EnsuresNonNull; | ||
| import org.checkerframework.checker.nullness.qual.MonotonicNonNull; | ||
| import org.mozilla.javascript.Context; | ||
| import org.mozilla.javascript.Function; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the same comments as in the prev. classes |
||
| import org.mozilla.javascript.ScriptableObject; | ||
|
|
@@ -44,8 +46,16 @@ public class JavaScriptDimFilter implements DimFilter | |
| private final ExtractionFn extractionFn; | ||
| private final JavaScriptConfig config; | ||
|
|
||
| // This variable is lazily initialized to avoid unnecessary JavaScript compilation during JSON serde | ||
| private JavaScriptPredicateFactory predicateFactory; | ||
| /** | ||
| * The field is declared volatile in order to ensure safe publication of the object | ||
| * in {@link JavaScriptPredicateFactory(String, ExtractionFn)} without worrying about final modifiers | ||
| * on the fields of the created object | ||
| * | ||
| * @see <a href="https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157"> | ||
| * https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157</a> | ||
| */ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention |
||
| @MonotonicNonNull | ||
| private volatile JavaScriptPredicateFactory predicateFactory; | ||
|
|
||
| @JsonCreator | ||
| public JavaScriptDimFilter( | ||
|
|
@@ -107,27 +117,32 @@ public DimFilter optimize() | |
| @Override | ||
| public Filter toFilter() | ||
| { | ||
| checkAndCreatePredicateFactory(); | ||
| JavaScriptPredicateFactory predicateFactory = getPredicateFactory(); | ||
| return new JavaScriptFilter(dimension, predicateFactory); | ||
| } | ||
|
|
||
| /** | ||
| * This class can be used by multiple threads, so this function should be thread-safe to avoid extra | ||
| * script compilation. | ||
| */ | ||
| private void checkAndCreatePredicateFactory() | ||
| @EnsuresNonNull("predicateFactory") | ||
| private JavaScriptPredicateFactory getPredicateFactory() | ||
| { | ||
| if (predicateFactory == null) { | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
| // JavaScript configuration should be checked when it's actually used because someone might still want Druid | ||
| // nodes to be able to deserialize JavaScript-based objects even though JavaScript is disabled. | ||
| Preconditions.checkState(config.isEnabled(), "JavaScript is disabled"); | ||
|
|
||
| JavaScriptPredicateFactory syncedFnPredicateFactory = predicateFactory; | ||
| if (syncedFnPredicateFactory == null) { | ||
| synchronized (config) { | ||
| if (predicateFactory == null) { | ||
| predicateFactory = new JavaScriptPredicateFactory(function, extractionFn); | ||
| syncedFnPredicateFactory = predicateFactory; | ||
| if (syncedFnPredicateFactory == null) { | ||
| syncedFnPredicateFactory = new JavaScriptPredicateFactory(function, extractionFn); | ||
| predicateFactory = syncedFnPredicateFactory; | ||
| } | ||
| } | ||
| } | ||
| return syncedFnPredicateFactory; | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realised that unlikely that many readers of this code will understand this passage without the reference to the thread. Please add
, see https://github.com/apache/incubator-druid/pull/6662#discussion_r237013157in the end