Skip to content

Commit c2d27b2

Browse files
committed
[Macros] Recovery after exectuable plugin crash
When executble plugins crashes or somehow decided to exit, the compiler should relaunch the plugin executable before sending another message.
1 parent e47f72c commit c2d27b2

File tree

8 files changed

+212
-52
lines changed

8 files changed

+212
-52
lines changed

include/swift/AST/CASTBridging.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ void Plugin_lock(PluginHandle handle);
307307
/// Unlock the plugin.
308308
void Plugin_unlock(PluginHandle handle);
309309

310+
/// Launch the plugin if it's not running.
311+
_Bool Plugin_spawnIfNeeded(PluginHandle handle);
312+
310313
/// Sends the message to the plugin, returns true if there was an error.
311314
/// Clients should receive the response by \c Plugin_waitForNextMessage .
312315
_Bool Plugin_sendMessage(PluginHandle handle, const BridgedData data);

include/swift/AST/PluginRegistry.h

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,69 @@
2323
namespace swift {
2424

2525
class LoadedExecutablePlugin {
26-
const llvm::sys::procid_t pid;
26+
27+
/// Represents the current process of the executable plugin.
28+
struct PluginProcess {
29+
const llvm::sys::procid_t pid;
30+
const int inputFileDescriptor;
31+
const int outputFileDescriptor;
32+
bool isStale = false;
33+
34+
PluginProcess(llvm::sys::procid_t pid, int inputFileDescriptor,
35+
int outputFileDescriptor);
36+
37+
~PluginProcess();
38+
39+
ssize_t write(const void *buf, size_t nbyte) const;
40+
ssize_t read(void *buf, size_t nbyte) const;
41+
};
42+
43+
/// Launched current process.
44+
std::unique_ptr<PluginProcess> Process;
45+
46+
/// Path to the plugin executable.
47+
const std::string ExecutablePath;
48+
49+
/// Last modification time of the `ExecutablePath` when this is initialized.
2750
const llvm::sys::TimePoint<> LastModificationTime;
28-
const int inputFileDescriptor;
29-
const int outputFileDescriptor;
3051

3152
/// Opaque value of the protocol capability of the pluugin. This is a
3253
/// value from ASTGen.
3354
const void *capability = nullptr;
3455

56+
/// Callbacks to be called when the connection is restored.
57+
llvm::SmallVector<std::function<void(void)> *, 0> onReconnect;
58+
3559
/// Cleanup function to call ASTGen.
3660
std::function<void(void)> cleanup;
3761

3862
std::mutex mtx;
3963

40-
ssize_t write(const void *buf, size_t nbyte) const;
41-
ssize_t read(void *buf, size_t nbyte) const;
42-
4364
public:
44-
LoadedExecutablePlugin(llvm::sys::procid_t pid,
45-
llvm::sys::TimePoint<> LastModificationTime,
46-
int inputFileDescriptor, int outputFileDescriptor);
65+
LoadedExecutablePlugin(llvm::StringRef ExecutablePath,
66+
llvm::sys::TimePoint<> LastModificationTime)
67+
: ExecutablePath(ExecutablePath),
68+
LastModificationTime(LastModificationTime){};
4769
~LoadedExecutablePlugin();
70+
71+
/// The last modification time of 'ExecutablePath' when this object is
72+
/// created.
4873
llvm::sys::TimePoint<> getLastModificationTime() const {
4974
return LastModificationTime;
5075
}
5176

77+
/// Indicates that the current process is usable.
78+
bool isAlive() const { return Process != nullptr && !Process->isStale; }
79+
80+
/// Mark the current process "stale".
81+
void setStale() const { Process->isStale = true; }
82+
5283
void lock() { mtx.lock(); }
5384
void unlock() { mtx.unlock(); }
5485

86+
// Launch the plugin if it's not already running, or it's stale.
87+
llvm::Error spawnIfNeeded();
88+
5589
/// Send a message to the plugin.
5690
llvm::Error sendMessage(llvm::StringRef message) const;
5791

@@ -63,7 +97,17 @@ class LoadedExecutablePlugin {
6397
this->cleanup = cleanup;
6498
}
6599

66-
llvm::sys::procid_t getPid() { return pid; }
100+
/// Add "on reconnect" callback.
101+
void addOnReconnect(std::function<void(void)> *fn) {
102+
onReconnect.push_back(fn);
103+
}
104+
105+
/// Remove "on reconnect" callback.
106+
void removeOnReconnect(std::function<void(void)> *fn) {
107+
llvm::erase_value(onReconnect, fn);
108+
}
109+
110+
llvm::sys::procid_t getPid() { return Process->pid; }
67111

68112
const void *getCapability() { return capability; };
69113
void setCapability(const void *newValue) { capability = newValue; };
@@ -78,7 +122,12 @@ class PluginRegistry {
78122
LoadedPluginExecutables;
79123

80124
public:
125+
/// Load a dynamic link library specified by \p path.
126+
/// If \p path plugin is already loaded, this returns the cached object.
81127
llvm::Expected<void *> loadLibraryPlugin(llvm::StringRef path);
128+
129+
/// Load an executable plugin specified by \p path .
130+
/// If \p path plugin is already loaded, this returns the cached object.
82131
llvm::Expected<LoadedExecutablePlugin *>
83132
loadExecutablePlugin(llvm::StringRef path);
84133

lib/AST/CASTBridging.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,14 @@ void Plugin_unlock(PluginHandle handle) {
649649
plugin->unlock();
650650
}
651651

652+
bool Plugin_spawnIfNeeded(PluginHandle handle) {
653+
auto *plugin = static_cast<LoadedExecutablePlugin *>(handle);
654+
auto error = plugin->spawnIfNeeded();
655+
bool hadError(error);
656+
llvm::consumeError(std::move(error));
657+
return hadError;
658+
}
659+
652660
bool Plugin_sendMessage(PluginHandle handle, const BridgedData data) {
653661
auto *plugin = static_cast<LoadedExecutablePlugin *>(handle);
654662
StringRef message(data.baseAddress, data.size);

lib/AST/PluginRegistry.cpp

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,41 @@ PluginRegistry::loadExecutablePlugin(StringRef path) {
9494
"not executable");
9595
}
9696

97+
plugin = std::unique_ptr<LoadedExecutablePlugin>(
98+
new LoadedExecutablePlugin(path, stat.getLastModificationTime()));
99+
100+
// Launch here to see if it's actually executable, and diagnose (by returning
101+
// an error) if necessary.
102+
if (auto error = plugin->spawnIfNeeded()) {
103+
plugin.reset();
104+
return std::move(error);
105+
}
106+
107+
return plugin.get();
108+
}
109+
110+
llvm::Error LoadedExecutablePlugin::spawnIfNeeded() {
111+
llvm::sys::fs::file_status stat;
112+
if (auto err = llvm::sys::fs::status(ExecutablePath, stat)) {
113+
return llvm::errorCodeToError(err);
114+
}
115+
116+
if (Process) {
117+
// See if the loaded one is still usable.
118+
if (!Process->isStale)
119+
return llvm::Error::success();
120+
121+
// NOTE: We don't check the mtime here because 'stat' call is too heavy.
122+
// PluginRegistry::loadExecutablePlugin() checks it and replace this object
123+
// itself if the plugin is updated. If the process is "stale" and we
124+
// relaunch the
125+
126+
// The plugin is stale. Discard the previously opened process.
127+
Process.reset();
128+
}
129+
97130
// Create command line arguments.
98-
SmallVector<StringRef, 4> command{path};
131+
SmallVector<StringRef, 4> command{ExecutablePath};
99132

100133
// Apply sandboxing.
101134
llvm::BumpPtrAllocator Allocator;
@@ -107,29 +140,36 @@ PluginRegistry::loadExecutablePlugin(StringRef path) {
107140
return llvm::errorCodeToError(childInfo.getError());
108141
}
109142

110-
plugin = std::unique_ptr<LoadedExecutablePlugin>(new LoadedExecutablePlugin(
111-
childInfo->Pid, stat.getLastModificationTime(),
112-
childInfo->ReadFileDescriptor, childInfo->WriteFileDescriptor));
143+
Process = std::unique_ptr<PluginProcess>(
144+
new PluginProcess(childInfo->Pid, childInfo->ReadFileDescriptor,
145+
childInfo->WriteFileDescriptor));
113146

114-
return plugin.get();
147+
// Call "on reconnect" callbacks.
148+
for (auto *cb : onReconnect) {
149+
(*cb)();
150+
}
151+
152+
return llvm::Error::success();
115153
}
116154

117-
LoadedExecutablePlugin::LoadedExecutablePlugin(
118-
llvm::sys::procid_t pid, llvm::sys::TimePoint<> LastModificationTime,
119-
int inputFileDescriptor, int outputFileDescriptor)
120-
: pid(pid), LastModificationTime(LastModificationTime),
121-
inputFileDescriptor(inputFileDescriptor),
155+
LoadedExecutablePlugin::PluginProcess::PluginProcess(llvm::sys::procid_t pid,
156+
int inputFileDescriptor,
157+
int outputFileDescriptor)
158+
: pid(pid), inputFileDescriptor(inputFileDescriptor),
122159
outputFileDescriptor(outputFileDescriptor) {}
123160

124-
LoadedExecutablePlugin::~LoadedExecutablePlugin() {
161+
LoadedExecutablePlugin::PluginProcess::~PluginProcess() {
125162
close(inputFileDescriptor);
126163
close(outputFileDescriptor);
164+
}
127165

166+
LoadedExecutablePlugin::~LoadedExecutablePlugin() {
128167
// Let ASTGen to cleanup things.
129168
this->cleanup();
130169
}
131170

132-
ssize_t LoadedExecutablePlugin::read(void *buf, size_t nbyte) const {
171+
ssize_t LoadedExecutablePlugin::PluginProcess::read(void *buf,
172+
size_t nbyte) const {
133173
ssize_t bytesToRead = nbyte;
134174
void *ptr = buf;
135175

@@ -154,7 +194,8 @@ ssize_t LoadedExecutablePlugin::read(void *buf, size_t nbyte) const {
154194
return nbyte - bytesToRead;
155195
}
156196

157-
ssize_t LoadedExecutablePlugin::write(const void *buf, size_t nbyte) const {
197+
ssize_t LoadedExecutablePlugin::PluginProcess::write(const void *buf,
198+
size_t nbyte) const {
158199
ssize_t bytesToWrite = nbyte;
159200
const void *ptr = buf;
160201

@@ -179,6 +220,7 @@ ssize_t LoadedExecutablePlugin::write(const void *buf, size_t nbyte) const {
179220
}
180221

181222
llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
223+
assert(isAlive());
182224
ssize_t writtenSize = 0;
183225

184226
const char *data = message.data();
@@ -187,15 +229,17 @@ llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
187229
// Write header (message size).
188230
uint64_t header = llvm::support::endian::byte_swap(
189231
uint64_t(size), llvm::support::endianness::little);
190-
writtenSize = write(&header, sizeof(header));
232+
writtenSize = Process->write(&header, sizeof(header));
191233
if (writtenSize != sizeof(header)) {
234+
setStale();
192235
return llvm::createStringError(llvm::inconvertibleErrorCode(),
193236
"failed to write plugin message header");
194237
}
195238

196239
// Write message.
197-
writtenSize = write(data, size);
240+
writtenSize = Process->write(data, size);
198241
if (writtenSize != ssize_t(size)) {
242+
setStale();
199243
return llvm::createStringError(llvm::inconvertibleErrorCode(),
200244
"failed to write plugin message data");
201245
}
@@ -204,13 +248,15 @@ llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
204248
}
205249

206250
llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
251+
assert(isAlive());
207252
ssize_t readSize = 0;
208253

209254
// Read header (message size).
210255
uint64_t header;
211-
readSize = read(&header, sizeof(header));
256+
readSize = Process->read(&header, sizeof(header));
212257

213258
if (readSize != sizeof(header)) {
259+
setStale();
214260
return llvm::createStringError(llvm::inconvertibleErrorCode(),
215261
"failed to read plugin message header");
216262
}
@@ -224,8 +270,9 @@ llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
224270
auto sizeToRead = size;
225271
while (sizeToRead > 0) {
226272
char buffer[4096];
227-
readSize = read(buffer, std::min(sizeof(buffer), sizeToRead));
273+
readSize = Process->read(buffer, std::min(sizeof(buffer), sizeToRead));
228274
if (readSize == 0) {
275+
setStale();
229276
return llvm::createStringError(llvm::inconvertibleErrorCode(),
230277
"failed to read plugin message data");
231278
}

lib/ASTGen/Sources/ASTGen/PluginHost.swift

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,30 +38,37 @@ public func _deinitializePlugin(
3838
}
3939

4040
/// Load the library plugin in the plugin server.
41+
/// This should be called inside lock.
4142
@_cdecl("swift_ASTGen_pluginServerLoadLibraryPlugin")
4243
func swift_ASTGen_pluginServerLoadLibraryPlugin(
4344
opaqueHandle: UnsafeMutableRawPointer,
4445
libraryPath: UnsafePointer<Int8>,
4546
moduleName: UnsafePointer<Int8>,
46-
cxxDiagnosticEngine: UnsafeMutablePointer<UInt8>
47+
cxxDiagnosticEngine: UnsafeMutablePointer<UInt8>?
4748
) -> Bool {
4849
let plugin = CompilerPlugin(opaqueHandle: opaqueHandle)
4950
assert(plugin.capability?.features.contains(.loadPluginLibrary) == true)
5051
let libraryPath = String(cString: libraryPath)
5152
let moduleName = String(cString: moduleName)
52-
let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: cxxDiagnosticEngine)
53+
54+
let diagEngine: PluginDiagnosticsEngine?
55+
if let cxxDiagnosticEngine = cxxDiagnosticEngine {
56+
diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: cxxDiagnosticEngine)
57+
} else {
58+
diagEngine = nil
59+
}
5360

5461
do {
55-
let result = try plugin.sendMessageAndWait(
62+
let result = try plugin.sendMessageAndWaitWithoutLock(
5663
.loadPluginLibrary(libraryPath: libraryPath, moduleName: moduleName)
5764
)
5865
guard case .loadPluginLibraryResult(let loaded, let diagnostics) = result else {
5966
throw PluginError.invalidReponseKind
6067
}
61-
diagEngine.emit(diagnostics);
68+
diagEngine?.emit(diagnostics);
6269
return loaded
6370
} catch {
64-
diagEngine.diagnose(error: error)
71+
diagEngine?.diagnose(error: error)
6572
return false
6673
}
6774
}
@@ -121,28 +128,31 @@ struct CompilerPlugin {
121128
return try LLVMJSON.decode(PluginToHostMessage.self, from: data)
122129
}
123130

131+
func sendMessageAndWaitWithoutLock(_ message: HostToPluginMessage) throws -> PluginToHostMessage {
132+
try sendMessage(message)
133+
return try waitForNextMessage()
134+
}
135+
124136
func sendMessageAndWait(_ message: HostToPluginMessage) throws -> PluginToHostMessage {
125137
try self.withLock {
126-
try sendMessage(message)
127-
return try waitForNextMessage()
138+
guard !Plugin_spawnIfNeeded(opaqueHandle) else {
139+
throw PluginError.failedToSendMessage
140+
}
141+
return try sendMessageAndWaitWithoutLock(message);
128142
}
129143
}
130144

145+
/// Initialize the plugin. This should be called inside lock.
131146
func initialize() {
132-
// Don't use `sendMessageAndWait` because we want to keep the lock until
133-
// setting the returned value.
134147
do {
135-
try self.withLock {
136-
// Get capability.
137-
try self.sendMessage(.getCapability)
138-
let response = try self.waitForNextMessage()
139-
guard case .getCapabilityResult(let capability) = response else {
140-
throw PluginError.invalidReponseKind
141-
}
142-
let ptr = UnsafeMutablePointer<Capability>.allocate(capacity: 1)
143-
ptr.initialize(to: .init(capability))
144-
Plugin_setCapability(opaqueHandle, UnsafeRawPointer(ptr))
148+
// Get capability.
149+
let response = try self.sendMessageAndWaitWithoutLock(.getCapability)
150+
guard case .getCapabilityResult(let capability) = response else {
151+
throw PluginError.invalidReponseKind
145152
}
153+
let ptr = UnsafeMutablePointer<Capability>.allocate(capacity: 1)
154+
ptr.initialize(to: .init(capability))
155+
Plugin_setCapability(opaqueHandle, UnsafeRawPointer(ptr))
146156
} catch {
147157
assertionFailure(String(describing: error))
148158
return

0 commit comments

Comments
 (0)