Skip to content

Commit 2641cf6

Browse files
committed
Use cached expanded load path for feature lookup
1 parent e68afb3 commit 2641cf6

File tree

6 files changed

+58
-6
lines changed

6 files changed

+58
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Bug fixes:
1414
* Fixed `String#encode` with options issue (#2091, #2095, @LillianZ)
1515
* Fixed issue with `spawn` when `:close` redirect is used (#2097).
1616
* Fixed `coverage` issue when `*eval` is used (#2078).
17+
* Use expanded load paths for feature matching (#1501).
1718

1819
Compatibility:
1920

spec/ruby/core/kernel/shared/require.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,17 @@
344344
loaded_feature = $LOADED_FEATURES.last
345345
ScratchPad.recorded.should == [loaded_feature]
346346
end
347+
348+
it "requires only once when a new matching file added to path" do
349+
@object.require('load_fixture').should be_true
350+
ScratchPad.recorded.should == [:loaded]
351+
352+
symlink_to_code_dir_two = tmp("codesymlinktwo")
353+
File.symlink("#{CODE_LOADING_DIR}/b", symlink_to_code_dir_two)
354+
$LOAD_PATH.unshift(symlink_to_code_dir_two)
355+
356+
@object.require('load_fixture').should be_false
357+
end
347358
end
348359

349360
describe "with symlinks in the required feature and $LOAD_PATH" do

src/main/java/org/truffleruby/core/kernel/KernelNodes.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import org.truffleruby.core.rope.RopeOperations;
7373
import org.truffleruby.core.string.RubyString;
7474
import org.truffleruby.core.string.StringCachingGuards;
75+
import org.truffleruby.core.string.StringNodes;
7576
import org.truffleruby.core.string.StringNodes.MakeStringNode;
7677
import org.truffleruby.core.string.StringOperations;
7778
import org.truffleruby.core.support.TypeNodes.CheckFrozenNode;
@@ -394,6 +395,19 @@ protected RubySymbol calleeName() {
394395
}
395396
}
396397

398+
@Primitive(name = "canonicalize_path")
399+
public abstract static class CanonicalizePathNode extends PrimitiveArrayArgumentsNode {
400+
401+
@Specialization
402+
@TruffleBoundary
403+
protected RubyString canonicalPath(RubyString string,
404+
@Cached StringNodes.MakeStringNode makeStringNode) {
405+
final String expandedPath = getContext().getFeatureLoader().canonicalize(string.getJavaString());
406+
return makeStringNode.executeMake(expandedPath, UTF8Encoding.INSTANCE, CodeRange.CR_UNKNOWN);
407+
}
408+
409+
}
410+
397411
@Primitive(name = "kernel_caller_locations", lowerFixnum = { 0, 1 })
398412
public abstract static class CallerLocationsNode extends CoreMethodArrayArgumentsNode {
399413

src/main/java/org/truffleruby/core/string/RubyString.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,16 @@ public RubyString(Shape shape, boolean frozen, boolean tainted, Rope rope) {
4242
this.rope = rope;
4343
}
4444

45+
/** should only be used for debugging */
4546
@Override
4647
public String toString() {
4748
return rope.toString();
4849
}
4950

51+
public String getJavaString() {
52+
return StringOperations.getString(this);
53+
}
54+
5055
// region RubyLibrary messages
5156
@ExportMessage
5257
public void freeze() {

src/main/java/org/truffleruby/language/loader/FeatureLoader.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.truffleruby.RubyLanguage;
2727
import org.truffleruby.collections.ConcurrentOperations;
2828
import org.truffleruby.core.array.ArrayOperations;
29+
import org.truffleruby.core.array.RubyArray;
2930
import org.truffleruby.core.encoding.EncodingManager;
3031
import org.truffleruby.core.module.RubyModule;
3132
import org.truffleruby.core.string.RubyString;
@@ -37,6 +38,7 @@
3738
import org.truffleruby.interop.TranslateInteropExceptionNode;
3839
import org.truffleruby.language.RubyConstant;
3940
import org.truffleruby.language.control.RaiseException;
41+
import org.truffleruby.language.dispatch.DispatchNode;
4042
import org.truffleruby.platform.NativeConfiguration;
4143
import org.truffleruby.platform.Platform;
4244
import org.truffleruby.platform.TruffleNFIPlatform;
@@ -280,9 +282,11 @@ public String findFeatureImpl(String feature) {
280282
if (feature.startsWith(RubyLanguage.RESOURCE_SCHEME) || new File(feature).isAbsolute()) {
281283
found = findFeatureWithAndWithoutExtension(feature);
282284
} else if (hasExtension(feature)) {
283-
for (Object pathObject : ArrayOperations.toIterable(context.getCoreLibrary().getLoadPath())) {
284-
// $LOAD_PATH entries are canonicalized since Ruby 2.4.4
285-
final String loadPath = canonicalize(pathObject.toString());
285+
final RubyArray expandedLoadPath = (RubyArray) DispatchNode.getUncached().call(
286+
context.getCoreLibrary().truffleFeatureLoaderModule,
287+
"get_expanded_load_path");
288+
for (Object pathObject : ArrayOperations.toIterable(expandedLoadPath)) {
289+
final String loadPath = ((RubyString) pathObject).getJavaString();
286290

287291
if (context.getOptions().LOG_FEATURE_LOCATION) {
288292
RubyLanguage.LOGGER.info(String.format("from load path %s...", loadPath));
@@ -298,9 +302,12 @@ public String findFeatureImpl(String feature) {
298302
}
299303
} else {
300304
extensionLoop: for (String extension : EXTENSIONS) {
301-
for (Object pathObject : ArrayOperations.toIterable(context.getCoreLibrary().getLoadPath())) {
305+
final RubyArray expandedLoadPath = (RubyArray) DispatchNode.getUncached().call(
306+
context.getCoreLibrary().truffleFeatureLoaderModule,
307+
"get_expanded_load_path");
308+
for (Object pathObject : ArrayOperations.toIterable(expandedLoadPath)) {
302309
// $LOAD_PATH entries are canonicalized since Ruby 2.4.4
303-
final String loadPath = canonicalize(pathObject.toString());
310+
final String loadPath = ((RubyString) pathObject).getJavaString();
304311

305312
if (context.getOptions().LOG_FEATURE_LOCATION) {
306313
RubyLanguage.LOGGER.info(String.format("from load path %s...", loadPath));

src/main/ruby/truffleruby/core/truffle/feature_loader.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ module FeatureLoader
1818
# A snapshot of $LOADED_FEATURES, to check if the @loaded_features_index cache is up to date.
1919
@loaded_features_copy = []
2020

21+
@expanded_load_path = []
22+
# A snapshot of $LOAD_PATH, to check if the @expanded_load_path cache is up to date.
23+
@load_path_copy = []
24+
2125
def self.clear_cache
2226
@loaded_features_index.clear
2327
@loaded_features_copy.clear
28+
@expanded_load_path.clear
29+
@load_path_copy.clear
2430
end
2531

2632
class FeatureEntry
@@ -149,7 +155,7 @@ def self.feature_provided?(feature, expanded)
149155
if expanded
150156
nil
151157
else
152-
loaded_feature_path(loaded_feature, feature, $LOAD_PATH)
158+
loaded_feature_path(loaded_feature, feature, get_expanded_load_path)
153159
end
154160
end
155161
if feature_path
@@ -271,5 +277,13 @@ def self.features_index_add(feature, offset)
271277
end
272278
end
273279

280+
def self.get_expanded_load_path
281+
unless Primitive.array_storage_equal?(@load_path_copy, $LOAD_PATH)
282+
@expanded_load_path = $LOAD_PATH.map { |path| Primitive.canonicalize_path(path) }
283+
@loaded_features_copy = $LOAD_PATH.dup
284+
end
285+
@expanded_load_path
286+
end
287+
274288
end
275289
end

0 commit comments

Comments
 (0)