Skip to content

Commit a49ab25

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 a49ab25

10 files changed

+241
-63
lines changed

Diff for: include/swift/AST/CASTBridging.h

+3
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);

Diff for: include/swift/AST/PluginRegistry.h

+69-10
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 it's fails to execute it.
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()` relaunched 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

Diff for: lib/AST/ASTContext.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,9 @@ struct ASTContext::Implementation {
528528
/// NOTE: Do not reference this directly. Use ASTContext::getPluginRegistry().
529529
PluginRegistry *Plugins = nullptr;
530530

531+
/// `Plugins` storage if this ASTContext owns it.
532+
std::unique_ptr<PluginRegistry> OwnedPluginRegistry = nullptr;
533+
531534
/// Cache of loaded symbols.
532535
llvm::StringMap<void *> LoadedSymbols;
533536

@@ -6255,9 +6258,7 @@ PluginRegistry *ASTContext::getPluginRegistry() const {
62556258
// Create a new one if it hasn't been set.
62566259
if (!registry) {
62576260
registry = new PluginRegistry();
6258-
const_cast<ASTContext *>(this)->addCleanup([registry]{
6259-
delete registry;
6260-
});
6261+
getImpl().OwnedPluginRegistry.reset(registry);
62616262
}
62626263

62636264
assert(registry != nullptr);

Diff for: lib/AST/CASTBridging.cpp

+8
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);

Diff for: lib/AST/PluginRegistry.cpp

+61-25
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) {
@@ -74,14 +71,14 @@ PluginRegistry::loadExecutablePlugin(StringRef path) {
7471
}
7572

7673
// See if the plugin is already loaded.
77-
auto &plugin = LoadedPluginExecutables[path];
78-
if (plugin) {
74+
auto &storage = LoadedPluginExecutables[path];
75+
if (storage) {
7976
// See if the loaded one is still usable.
80-
if (plugin->getLastModificationTime() == stat.getLastModificationTime())
81-
return plugin.get();
77+
if (storage->getLastModificationTime() == stat.getLastModificationTime())
78+
return storage.get();
8279

8380
// The plugin is updated. Close the previously opened plugin.
84-
plugin = nullptr;
81+
storage = nullptr;
8582
}
8683

8784
if (!llvm::sys::fs::exists(stat)) {
@@ -94,8 +91,35 @@ PluginRegistry::loadExecutablePlugin(StringRef path) {
9491
"not executable");
9592
}
9693

94+
auto 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+
return std::move(error);
101+
}
102+
103+
storage = std::move(plugin);
104+
return storage.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));
137+
138+
// Call "on reconnect" callbacks.
139+
for (auto *callback : onReconnect) {
140+
(*callback)();
141+
}
113142

114-
return plugin.get();
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

@@ -187,15 +219,17 @@ llvm::Error LoadedExecutablePlugin::sendMessage(llvm::StringRef message) const {
187219
// Write header (message size).
188220
uint64_t header = llvm::support::endian::byte_swap(
189221
uint64_t(size), llvm::support::endianness::little);
190-
writtenSize = write(&header, sizeof(header));
222+
writtenSize = Process->write(&header, sizeof(header));
191223
if (writtenSize != sizeof(header)) {
224+
setStale();
192225
return llvm::createStringError(llvm::inconvertibleErrorCode(),
193226
"failed to write plugin message header");
194227
}
195228

196229
// Write message.
197-
writtenSize = write(data, size);
230+
writtenSize = Process->write(data, size);
198231
if (writtenSize != ssize_t(size)) {
232+
setStale();
199233
return llvm::createStringError(llvm::inconvertibleErrorCode(),
200234
"failed to write plugin message data");
201235
}
@@ -208,9 +242,10 @@ llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
208242

209243
// Read header (message size).
210244
uint64_t header;
211-
readSize = read(&header, sizeof(header));
245+
readSize = Process->read(&header, sizeof(header));
212246

213247
if (readSize != sizeof(header)) {
248+
setStale();
214249
return llvm::createStringError(llvm::inconvertibleErrorCode(),
215250
"failed to read plugin message header");
216251
}
@@ -224,8 +259,9 @@ llvm::Expected<std::string> LoadedExecutablePlugin::waitForNextMessage() const {
224259
auto sizeToRead = size;
225260
while (sizeToRead > 0) {
226261
char buffer[4096];
227-
readSize = read(buffer, std::min(sizeof(buffer), sizeToRead));
262+
readSize = Process->read(buffer, std::min(sizeof(buffer), sizeToRead));
228263
if (readSize == 0) {
264+
setStale();
229265
return llvm::createStringError(llvm::inconvertibleErrorCode(),
230266
"failed to read plugin message data");
231267
}

0 commit comments

Comments
 (0)