Skip to content

Commit a54cc5f

Browse files
Yifu Wangfacebook-github-bot
authored andcommitted
make allocators and sanitizers work for processes created with multiprocessing's spawn method in dev mode (facebook#2657)
Summary: Pull Request resolved: facebook#2657 #### Problem Currently, the entrypoint for in-place Python binaries (i.e. built with dev mode) executes the following steps to load system native dependencies (e.g. sanitizers and allocators): - Backup `LD_PRELOAD` set by the caller - Append system native dependencies to `LD_PRELOAD` - Inject a prologue in user code which restores `LD_PRELOAD` set by the caller - `execv` Python interpreter The steps work as intended for single process Python programs. However, when a Python program spawns child processes, the child processes will not load native dependencies, since they simply `execv`'s the vanilla Python interpreter. A few examples why this is problematic: - The ASAN runtime library is a system native dependency. Without loading it, a child process that loads user native dependencies compiled with ASAN will crash during static initialization because it can't find `_asan_init`. - `jemalloc` is also a system native dependency. Many if not most ML use cases "bans" dev mode because of these problems. It is very unfortunate considering the developer efficiency dev mode provides. In addition, a huge amount of unit tests have to run in a more expensive build mode because of these problems. For an earlier discussion, see [this post](https://fb.workplace.com/groups/fbpython/permalink/2897630276944987/). #### Solution Move the system native dependencies loading logic out of the Python binary entrypoint into an interpreter wrapper, and set the interpreter as `sys.executable` in the injected prologue: - The Python binary entrypoint now uses the interpreter wrapper, which has the same command line interface as the Python interpreter, to run the main module. - `multiprocessing`'s `spawn` method now uses the interpreter wrapper to create child processes, ensuring system native dependencies get loaded correctly. #### Alternative Considered One alternative considered is to simply not removing system native dependencies from `LD_PRELOAD`, so they are present in the spawned processes. However, this causes some linking issues, which were perhaps the reason `LD_PRELOAD` was restored in the first place: in-place Python binaries have access to binaries install on devservers that are not built with the target platform (e.g. `/bin/sh` which is used by some Python standard libraries). These binaries does not link properly with the system native dependencies. #### References An old RFC for this change: D16210828 The counterpart for opt mode: D16350169 fbshipit-source-id: 118d3a4657ba397b1c98b95d62f85ad01e234422
1 parent d687248 commit a54cc5f

File tree

5 files changed

+295
-153
lines changed

5 files changed

+295
-153
lines changed

build.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@
10161016
<include name="com/facebook/buck/maven/build-file.st"/>
10171017
<include name="com/facebook/buck/python/*.py"/>
10181018
<include name="com/facebook/buck/python/run_inplace.py.in"/>
1019+
<include name="com/facebook/buck/python/run_inplace_interpreter_wrapper.py.in"/>
10191020
<include name="com/facebook/buck/python/run_inplace_lite.py.in"/>
10201021
<include name="com/facebook/buck/parser/function/BuckPyFunction.stg"/>
10211022
<include name="com/facebook/buck/shell/sh_binary_template"/>

src/com/facebook/buck/features/python/BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ java_library_with_plugins(
6969
"__test_main__.py",
7070
"compile.py",
7171
"run_inplace.py.in",
72+
"run_inplace_interpreter_wrapper.py.in",
7273
"run_inplace_lite.py.in",
7374
],
7475
tests = [

src/com/facebook/buck/features/python/PythonInPlaceBinary.java

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@
1818

1919
import com.facebook.buck.core.build.buildable.context.BuildableContext;
2020
import com.facebook.buck.core.build.context.BuildContext;
21+
import com.facebook.buck.core.filesystems.AbsPath;
2122
import com.facebook.buck.core.filesystems.RelPath;
2223
import com.facebook.buck.core.model.BuildTarget;
2324
import com.facebook.buck.core.model.OutputLabel;
2425
import com.facebook.buck.core.model.TargetConfiguration;
26+
import com.facebook.buck.core.model.impl.BuildTargetPaths;
2527
import com.facebook.buck.core.rulekey.AddToRuleKey;
2628
import com.facebook.buck.core.rules.BuildRule;
2729
import com.facebook.buck.core.rules.BuildRuleResolver;
2830
import com.facebook.buck.core.rules.attr.HasRuntimeDeps;
2931
import com.facebook.buck.core.rules.impl.SymlinkTree;
32+
import com.facebook.buck.core.sourcepath.ExplicitBuildTargetSourcePath;
3033
import com.facebook.buck.core.toolchain.tool.Tool;
3134
import com.facebook.buck.core.toolchain.tool.impl.CommandTool;
3235
import com.facebook.buck.cxx.toolchain.CxxPlatform;
@@ -39,6 +42,7 @@
3942
import com.facebook.buck.step.Step;
4043
import com.facebook.buck.step.fs.MkdirStep;
4144
import com.facebook.buck.step.isolatedsteps.common.WriteFileIsolatedStep;
45+
import com.facebook.buck.test.selectors.Nullable;
4246
import com.facebook.buck.util.Escaper;
4347
import com.facebook.buck.util.stream.RichStream;
4448
import com.google.common.base.Joiner;
@@ -57,6 +61,8 @@
5761
public class PythonInPlaceBinary extends PythonBinary implements HasRuntimeDeps {
5862

5963
private static final String RUN_INPLACE_RESOURCE = "run_inplace.py.in";
64+
private static final String RUN_INPLACE_INTERPRETER_WRAPPER_RESOURCE =
65+
"run_inplace_interpreter_wrapper.py.in";
6066
private static final String RUN_INPLACE_LITE_RESOURCE = "run_inplace_lite.py.in";
6167

6268
// TODO(agallagher): Task #8098647: This rule has no steps, so it
@@ -68,8 +74,10 @@ public class PythonInPlaceBinary extends PythonBinary implements HasRuntimeDeps
6874
//
6975
// We should upate the Python test rule to account for this.
7076
private final SymlinkTree linkTree;
77+
private final RelPath interpreterWrapperGenPath;
7178
@AddToRuleKey private final Tool python;
72-
@AddToRuleKey private final Supplier<String> script;
79+
@AddToRuleKey private final Supplier<String> binScript;
80+
@AddToRuleKey private final Supplier<String> interpreterWrapperScript;
7381

7482
PythonInPlaceBinary(
7583
BuildTarget buildTarget,
@@ -98,18 +106,28 @@ public class PythonInPlaceBinary extends PythonBinary implements HasRuntimeDeps
98106
legacyOutputPath);
99107
this.linkTree = linkTree;
100108
this.python = python;
101-
this.script =
102-
getScript(
109+
this.interpreterWrapperGenPath =
110+
getInterpreterWrapperGenPath(
111+
buildTarget, projectFilesystem, pexExtension, legacyOutputPath);
112+
AbsPath targetRoot =
113+
projectFilesystem
114+
.resolve(getBinPath(buildTarget, projectFilesystem, pexExtension, legacyOutputPath))
115+
.getParent();
116+
this.binScript =
117+
getBinScript(
118+
pythonPlatform,
119+
mainModule,
120+
targetRoot.relativize(linkTree.getRoot()),
121+
targetRoot.relativize(projectFilesystem.resolve(interpreterWrapperGenPath)),
122+
packageStyle);
123+
this.interpreterWrapperScript =
124+
getInterpreterWrapperScript(
103125
ruleResolver,
104126
buildTarget.getTargetConfiguration(),
105127
pythonPlatform,
106128
cxxPlatform,
107-
mainModule,
108129
components,
109-
projectFilesystem
110-
.resolve(getBinPath(buildTarget, projectFilesystem, pexExtension, legacyOutputPath))
111-
.getParent()
112-
.relativize(linkTree.getRoot()),
130+
targetRoot.relativize(linkTree.getRoot()),
113131
preloadLibraries,
114132
packageStyle);
115133
}
@@ -123,6 +141,10 @@ private static String getRunInplaceResource() {
123141
return getNamedResource(RUN_INPLACE_RESOURCE);
124142
}
125143

144+
private static String getRunInplaceInterpreterWrapperResource() {
145+
return getNamedResource(RUN_INPLACE_INTERPRETER_WRAPPER_RESOURCE);
146+
}
147+
126148
private static String getRunInplaceLiteResource() {
127149
return getNamedResource(RUN_INPLACE_LITE_RESOURCE);
128150
}
@@ -136,29 +158,64 @@ private static String getNamedResource(String resourceName) {
136158
}
137159
}
138160

139-
private static Supplier<String> getScript(
161+
private static RelPath getInterpreterWrapperGenPath(
162+
BuildTarget target,
163+
ProjectFilesystem filesystem,
164+
String extension,
165+
boolean legacyOutputPath) {
166+
if (!legacyOutputPath) {
167+
target = target.withFlavors();
168+
}
169+
return BuildTargetPaths.getGenPath(
170+
filesystem.getBuckPaths(), target, "%s#interpreter_wrapper" + extension);
171+
}
172+
173+
private static Supplier<String> getBinScript(
174+
PythonPlatform pythonPlatform,
175+
String mainModule,
176+
RelPath linkTreeRoot,
177+
RelPath interpreterWrapperPath,
178+
PackageStyle packageStyle) {
179+
return () -> {
180+
String linkTreeRootStr = Escaper.escapeAsPythonString(linkTreeRoot.toString());
181+
String interpreterWrapperPathStr =
182+
Escaper.escapeAsPythonString(interpreterWrapperPath.toString());
183+
return new ST(
184+
new STGroup(),
185+
packageStyle == PackageStyle.INPLACE
186+
? getRunInplaceResource()
187+
: getRunInplaceLiteResource())
188+
.add("PYTHON", pythonPlatform.getEnvironment().getPythonPath())
189+
.add("PYTHON_INTERPRETER_FLAGS", pythonPlatform.getInplaceBinaryInterpreterFlags())
190+
.add("MODULES_DIR", linkTreeRootStr)
191+
.add("MAIN_MODULE", Escaper.escapeAsPythonString(mainModule))
192+
.add("INTERPRETER_WRAPPER_REL_PATH", interpreterWrapperPathStr)
193+
.render();
194+
};
195+
}
196+
197+
@Nullable
198+
private static Supplier<String> getInterpreterWrapperScript(
140199
BuildRuleResolver resolver,
141200
TargetConfiguration targetConfiguration,
142201
PythonPlatform pythonPlatform,
143202
CxxPlatform cxxPlatform,
144-
String mainModule,
145203
PythonPackageComponents components,
146204
RelPath relativeLinkTreeRoot,
147205
ImmutableSet<String> preloadLibraries,
148206
PackageStyle packageStyle) {
149207
String relativeLinkTreeRootStr = Escaper.escapeAsPythonString(relativeLinkTreeRoot.toString());
150208
Linker ld = cxxPlatform.getLd().resolve(resolver, targetConfiguration);
209+
// Lite mode doesn't need an interpreter wrapper as there's no LD_PRELOADs involved.
210+
if (packageStyle != PackageStyle.INPLACE) {
211+
return null;
212+
}
151213
return () -> {
152214
ST st =
153-
new ST(
154-
new STGroup(),
155-
packageStyle == PackageStyle.INPLACE
156-
? getRunInplaceResource()
157-
: getRunInplaceLiteResource())
215+
new ST(new STGroup(), getRunInplaceInterpreterWrapperResource())
158216
.add("PYTHON", pythonPlatform.getEnvironment().getPythonPath())
159-
.add("MAIN_MODULE", Escaper.escapeAsPythonString(mainModule))
160-
.add("MODULES_DIR", relativeLinkTreeRootStr)
161-
.add("PYTHON_INTERPRETER_FLAGS", pythonPlatform.getInplaceBinaryInterpreterFlags());
217+
.add("PYTHON_INTERPRETER_FLAGS", pythonPlatform.getInplaceBinaryInterpreterFlags())
218+
.add("MODULES_DIR", relativeLinkTreeRootStr);
162219

163220
// Only add platform-specific values when the binary includes native libraries.
164221
if (components.getNativeLibraries().getComponents().isEmpty()) {
@@ -187,11 +244,26 @@ public ImmutableList<Step> getBuildSteps(
187244
BuildContext context, BuildableContext buildableContext) {
188245
RelPath binPath = context.getSourcePathResolver().getCellUnsafeRelPath(getSourcePathToOutput());
189246
buildableContext.recordArtifact(binPath.getPath());
190-
return ImmutableList.of(
191-
MkdirStep.of(
192-
BuildCellRelativePath.fromCellRelativePath(
193-
context.getBuildCellRootPath(), getProjectFilesystem(), binPath.getParent())),
194-
WriteFileIsolatedStep.of(script, binPath, /* executable */ true));
247+
ImmutableList.Builder<Step> stepsBuilder = new ImmutableList.Builder<Step>();
248+
stepsBuilder
249+
.add(
250+
MkdirStep.of(
251+
BuildCellRelativePath.fromCellRelativePath(
252+
context.getBuildCellRootPath(), getProjectFilesystem(), binPath.getParent())))
253+
.add(WriteFileIsolatedStep.of(binScript, binPath, /* executable */ true));
254+
255+
if (interpreterWrapperScript != null) {
256+
RelPath interpreterWrapperPath =
257+
context
258+
.getSourcePathResolver()
259+
.getCellUnsafeRelPath(
260+
ExplicitBuildTargetSourcePath.of(getBuildTarget(), interpreterWrapperGenPath));
261+
buildableContext.recordArtifact(interpreterWrapperPath.getPath());
262+
stepsBuilder.add(
263+
WriteFileIsolatedStep.of(
264+
interpreterWrapperScript, interpreterWrapperPath, /* executable */ true));
265+
}
266+
return stepsBuilder.build();
195267
}
196268

197269
@Override

0 commit comments

Comments
 (0)