Skip to content

Commit ade92be

Browse files
committed
[Macros] Recovery after executable plugin crash
When executable plugins crashed or somehow decided to exit, the compiler should relaunch the plugin executable before sending another message.
1 parent e93e7d1 commit ade92be

File tree

9 files changed

+234
-55
lines changed

9 files changed

+234
-55
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: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,79 @@
2222

2323
namespace swift {
2424

25+
/// Represent a "resolved" exectuable plugin.
26+
///
27+
/// Plugin clients usually deal with this object to communicate with the actual
28+
/// plugin implementation.
29+
/// This object has a file path of the plugin executable, and is responsible to
30+
/// launch it and manages the process. When the plugin process crashes, this
31+
/// should automatically relaunch the process so the clients can keep using this
32+
/// object as the interface.
2533
class LoadedExecutablePlugin {
26-
const llvm::sys::procid_t pid;
34+
35+
/// Represents the current process of the executable plugin.
36+
struct PluginProcess {
37+
const llvm::sys::procid_t pid;
38+
const int inputFileDescriptor;
39+
const int outputFileDescriptor;
40+
bool isStale = false;
41+
42+
PluginProcess(llvm::sys::procid_t pid, int inputFileDescriptor,
43+
int outputFileDescriptor);
44+
45+
~PluginProcess();
46+
47+
ssize_t write(const void *buf, size_t nbyte) const;
48+
ssize_t read(void *buf, size_t nbyte) const;
49+
};
50+
51+
/// Launched current process.
52+
std::unique_ptr<PluginProcess> Process;
53+
54+
/// Path to the plugin executable.
55+
const std::string ExecutablePath;
56+
57+
/// Last modification time of the `ExecutablePath` when this is initialized.
2758
const llvm::sys::TimePoint<> LastModificationTime;
28-
const int inputFileDescriptor;
29-
const int outputFileDescriptor;
3059

3160
/// Opaque value of the protocol capability of the pluugin. This is a
3261
/// value from ASTGen.
3362
const void *capability = nullptr;
3463

64+
/// Callbacks to be called when the connection is restored.
65+
llvm::SmallVector<std::function<void(void)> *, 0> onReconnect;
66+
3567
/// Cleanup function to call ASTGen.
3668
std::function<void(void)> cleanup;
3769

3870
std::mutex mtx;
3971

40-
ssize_t write(const void *buf, size_t nbyte) const;
41-
ssize_t read(void *buf, size_t nbyte) const;
42-
4372
public:
44-
LoadedExecutablePlugin(llvm::sys::procid_t pid,
45-
llvm::sys::TimePoint<> LastModificationTime,
46-
int inputFileDescriptor, int outputFileDescriptor);
73+
LoadedExecutablePlugin(llvm::StringRef ExecutablePath,
74+
llvm::sys::TimePoint<> LastModificationTime)
75+
: ExecutablePath(ExecutablePath),
76+
LastModificationTime(LastModificationTime){};
4777
~LoadedExecutablePlugin();
78+
79+
/// The last modification time of 'ExecutablePath' when this object is
80+
/// created.
4881
llvm::sys::TimePoint<> getLastModificationTime() const {
4982
return LastModificationTime;
5083
}
5184

85+
/// Indicates that the current process is usable.
86+
bool isAlive() const { return Process != nullptr && !Process->isStale; }
87+
88+
/// Mark the current process "stale".
89+
void setStale() const { Process->isStale = true; }
90+
5291
void lock() { mtx.lock(); }
5392
void unlock() { mtx.unlock(); }
5493

94+
// Launch the plugin if it's not already running, or it's stale. Return an
95+
// error if
96+
llvm::Error spawnIfNeeded();
97+
5598
/// Send a message to the plugin.
5699
llvm::Error sendMessage(llvm::StringRef message) const;
57100

@@ -63,7 +106,18 @@ class LoadedExecutablePlugin {
63106
this->cleanup = cleanup;
64107
}
65108

66-
llvm::sys::procid_t getPid() { return pid; }
109+
/// Add "on reconnect" callback.
110+
/// These callbacks are called when `spawnIfNeeded()` relanched the plugin.
111+
void addOnReconnect(std::function<void(void)> *fn) {
112+
onReconnect.push_back(fn);
113+
}
114+
115+
/// Remove "on reconnect" callback.
116+
void removeOnReconnect(std::function<void(void)> *fn) {
117+
llvm::erase_value(onReconnect, fn);
118+
}
119+
120+
llvm::sys::procid_t getPid() { return Process->pid; }
67121

68122
const void *getCapability() { return capability; };
69123
void setCapability(const void *newValue) { capability = newValue; };
@@ -78,7 +132,12 @@ class PluginRegistry {
78132
LoadedPluginExecutables;
79133

80134
public:
135+
/// Load a dynamic link library specified by \p path.
136+
/// If \p path plugin is already loaded, this returns the cached object.
81137
llvm::Expected<void *> loadLibraryPlugin(llvm::StringRef path);
138+
139+
/// Load an executable plugin specified by \p path .
140+
/// If \p path plugin is already loaded, this returns the cached object.
82141
llvm::Expected<LoadedExecutablePlugin *>
83142
loadExecutablePlugin(llvm::StringRef path);
84143

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: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@
3838
#include <io.h>
3939
#endif
4040

41-
extern "C" const void *swift_ASTGen_getCompilerPluginCapability(void *handle);
42-
extern "C" void swift_ASTGen_destroyCompilerPluginCapability(void *value);
43-
4441
using namespace swift;
4542

4643
llvm::Expected<void *> PluginRegistry::loadLibraryPlugin(StringRef path) {
@@ -94,8 +91,35 @@ PluginRegistry::loadExecutablePlugin(StringRef path) {
9491
"not executable");
9592
}
9693

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

100124
// Apply sandboxing.
101125
llvm::BumpPtrAllocator Allocator;
@@ -107,29 +131,36 @@ PluginRegistry::loadExecutablePlugin(StringRef path) {
107131
return llvm::errorCodeToError(childInfo.getError());
108132
}
109133

110-
plugin = std::unique_ptr<LoadedExecutablePlugin>(new LoadedExecutablePlugin(
111-
childInfo->Pid, stat.getLastModificationTime(),
112-
childInfo->ReadFileDescriptor, childInfo->WriteFileDescriptor));
134+
Process = std::unique_ptr<PluginProcess>(
135+
new PluginProcess(childInfo->Pid, childInfo->ReadFileDescriptor,
136+
childInfo->WriteFileDescriptor));
113137

114-
return plugin.get();
138+
// Call "on reconnect" callbacks.
139+
for (auto *callback : onReconnect) {
140+
(*callback)();
141+
}
142+
143+
return llvm::Error::success();
115144
}
116145

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),
146+
LoadedExecutablePlugin::PluginProcess::PluginProcess(llvm::sys::procid_t pid,
147+
int inputFileDescriptor,
148+
int outputFileDescriptor)
149+
: pid(pid), inputFileDescriptor(inputFileDescriptor),
122150
outputFileDescriptor(outputFileDescriptor) {}
123151

124-
LoadedExecutablePlugin::~LoadedExecutablePlugin() {
152+
LoadedExecutablePlugin::PluginProcess::~PluginProcess() {
125153
close(inputFileDescriptor);
126154
close(outputFileDescriptor);
155+
}
127156

157+
LoadedExecutablePlugin::~LoadedExecutablePlugin() {
128158
// Let ASTGen to cleanup things.
129159
this->cleanup();
130160
}
131161

132-
ssize_t LoadedExecutablePlugin::read(void *buf, size_t nbyte) const {
162+
ssize_t LoadedExecutablePlugin::PluginProcess::read(void *buf,
163+
size_t nbyte) const {
133164
ssize_t bytesToRead = nbyte;
134165
void *ptr = buf;
135166

@@ -154,7 +185,8 @@ ssize_t LoadedExecutablePlugin::read(void *buf, size_t nbyte) const {
154185
return nbyte - bytesToRead;
155186
}
156187

157-
ssize_t LoadedExecutablePlugin::write(const void *buf, size_t nbyte) const {
188+
ssize_t LoadedExecutablePlugin::PluginProcess::write(const void *buf,
189+
size_t nbyte) const {
158190
ssize_t bytesToWrite = nbyte;
159191
const void *ptr = buf;
160192

@@ -179,6 +211,7 @@ ssize_t LoadedExecutablePlugin::write(const void *buf, size_t nbyte) const {
179211
}
180212

181213
llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
214+
assert(isAlive());
182215
ssize_t writtenSize = 0;
183216

184217
const char *data = message.data();
@@ -187,15 +220,17 @@ llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
187220
// Write header (message size).
188221
uint64_t header = llvm::support::endian::byte_swap(
189222
uint64_t(size), llvm::support::endianness::little);
190-
writtenSize = write(&header, sizeof(header));
223+
writtenSize = Process->write(&header, sizeof(header));
191224
if (writtenSize != sizeof(header)) {
225+
setStale();
192226
return llvm::createStringError(llvm::inconvertibleErrorCode(),
193227
"failed to write plugin message header");
194228
}
195229

196230
// Write message.
197-
writtenSize = write(data, size);
231+
writtenSize = Process->write(data, size);
198232
if (writtenSize != ssize_t(size)) {
233+
setStale();
199234
return llvm::createStringError(llvm::inconvertibleErrorCode(),
200235
"failed to write plugin message data");
201236
}
@@ -204,13 +239,15 @@ llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
204239
}
205240

206241
llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
242+
assert(isAlive());
207243
ssize_t readSize = 0;
208244

209245
// Read header (message size).
210246
uint64_t header;
211-
readSize = read(&header, sizeof(header));
247+
readSize = Process->read(&header, sizeof(header));
212248

213249
if (readSize != sizeof(header)) {
250+
setStale();
214251
return llvm::createStringError(llvm::inconvertibleErrorCode(),
215252
"failed to read plugin message header");
216253
}
@@ -224,8 +261,9 @@ llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
224261
auto sizeToRead = size;
225262
while (sizeToRead > 0) {
226263
char buffer[4096];
227-
readSize = read(buffer, std::min(sizeof(buffer), sizeToRead));
264+
readSize = Process->read(buffer, std::min(sizeof(buffer), sizeToRead));
228265
if (readSize == 0) {
266+
setStale();
229267
return llvm::createStringError(llvm::inconvertibleErrorCode(),
230268
"failed to read plugin message data");
231269
}

0 commit comments

Comments
 (0)