[sourcekit] Fix non-deterministic failure in CompileNotifications tests

This is a test-only change except for the introduction of a new request
that is only used by tests.

Our notifications are dispatched in the XPC event handler, which is not
synchronized with replies to explicit XPC send_message_with_reply calls.
This is fine for most users of sourcekitd, since the notifications are
already enqueued on the client side, but for testing we need a way to
guarantee that all notifications are passed to the client-side handler
before we exit. This commit introduces a new request for testing that
triggers a notification, allowing the client to wait on that
notification to ensure all previously posted notifications have been
handled.

Note: the non-deterministic test failures can be triggered by adding a
sleep of ~100 ms in the event handler before the notification is
dispatched to the main queue.

rdar://40311995
This commit is contained in:
Ben Langmuir
2018-06-12 12:37:44 -07:00
parent 197593b4aa
commit a88c73fe60
3 changed files with 42 additions and 5 deletions

View File

@@ -101,11 +101,13 @@ static SourceKitRequest ActiveRequest = SourceKitRequest::None;
static sourcekitd_uid_t SemaDiagnosticStage; static sourcekitd_uid_t SemaDiagnosticStage;
static sourcekitd_uid_t NoteDocUpdate; static sourcekitd_uid_t NoteDocUpdate;
static SourceKit::Semaphore semaSemaphore(0); static SourceKit::Semaphore semaSemaphore(0);
static sourcekitd_response_t semaResponse; static sourcekitd_response_t semaResponse;
static const char *semaName; static const char *semaName;
static sourcekitd_uid_t NoteTest;
static SourceKit::Semaphore noteSyncSemaphore(0);
namespace { namespace {
struct AsyncResponseInfo { struct AsyncResponseInfo {
SourceKit::Semaphore semaphore{0}; SourceKit::Semaphore semaphore{0};
@@ -139,7 +141,28 @@ struct NotificationBuffer {
}; };
static NotificationBuffer notificationBuffer; static NotificationBuffer notificationBuffer;
static void printBufferedNotifications() { static void syncNotificationsWithService() {
// Send TestNotification request, then wait for the notification. This ensures
// that all notifications previously posted on the service side have been
// passed to our notification handler.
sourcekitd_object_t req = sourcekitd_request_dictionary_create(nullptr, nullptr, 0);
sourcekitd_request_dictionary_set_uid(req, KeyRequest, RequestTestNotification);
auto resp = sourcekitd_send_request_sync(req);
if (sourcekitd_response_is_error(resp)) {
sourcekitd_response_description_dump(resp);
exit(1);
}
sourcekitd_response_dispose(resp);
sourcekitd_request_release(req);
if (noteSyncSemaphore.wait(60 * 1000)) {
llvm::report_fatal_error("Test notification not received");
}
}
static void printBufferedNotifications(bool syncWithService = true) {
if (syncWithService) {
syncNotificationsWithService();
}
notificationBuffer.handleNotifications([](sourcekitd_response_t note) { notificationBuffer.handleNotifications([](sourcekitd_response_t note) {
sourcekitd_response_description_dump_filedesc(note, STDOUT_FILENO); sourcekitd_response_description_dump_filedesc(note, STDOUT_FILENO);
}); });
@@ -171,6 +194,7 @@ static int skt_main(int argc, const char **argv) {
SemaDiagnosticStage = sourcekitd_uid_get_from_cstr("source.diagnostic.stage.swift.sema"); SemaDiagnosticStage = sourcekitd_uid_get_from_cstr("source.diagnostic.stage.swift.sema");
NoteDocUpdate = sourcekitd_uid_get_from_cstr("source.notification.editor.documentupdate"); NoteDocUpdate = sourcekitd_uid_get_from_cstr("source.notification.editor.documentupdate");
NoteTest = sourcekitd_uid_get_from_cstr("source.notification.test");
#define REQUEST(NAME, CONTENT) Request##NAME = sourcekitd_uid_get_from_cstr(CONTENT); #define REQUEST(NAME, CONTENT) Request##NAME = sourcekitd_uid_get_from_cstr(CONTENT);
#define KIND(NAME, CONTENT) Kind##NAME = sourcekitd_uid_get_from_cstr(CONTENT); #define KIND(NAME, CONTENT) Kind##NAME = sourcekitd_uid_get_from_cstr(CONTENT);
@@ -342,11 +366,12 @@ static int handleTestInvocation(ArrayRef<const char *> Args,
assert(Opts.repeatRequest >= 1); assert(Opts.repeatRequest >= 1);
for (unsigned i = 0; i < Opts.repeatRequest; ++i) { for (unsigned i = 0; i < Opts.repeatRequest; ++i) {
int ret = handleTestInvocation(Opts, InitOpts); if (int ret = handleTestInvocation(Opts, InitOpts)) {
printBufferedNotifications(); printBufferedNotifications(/*syncWithService=*/true);
if (ret) {
return ret; return ret;
} }
// We will sync with the service before exiting; don't do so here.
printBufferedNotifications(/*syncWithService=*/false);
} }
return 0; return 0;
} }
@@ -1169,6 +1194,8 @@ static void notification_receiver(sourcekitd_response_t resp) {
semaResponse = sourcekitd_send_request_sync(edReq); semaResponse = sourcekitd_send_request_sync(edReq);
sourcekitd_request_release(edReq); sourcekitd_request_release(edReq);
semaSemaphore.signal(); semaSemaphore.signal();
} else if (note == NoteTest) {
noteSyncSemaphore.signal();
} else { } else {
notificationBuffer.add(resp); notificationBuffer.add(resp);
} }

View File

@@ -322,6 +322,15 @@ void handleRequestImpl(sourcekitd_object_t ReqObj, ResponseReceiver Rec) {
::exit(1); ::exit(1);
} }
if (ReqUID == RequestTestNotification) {
static UIdent TestNotification("source.notification.test");
ResponseBuilder RespBuilder;
auto Dict = RespBuilder.getDictionary();
Dict.set(KeyNotification, TestNotification);
sourcekitd::postNotification(RespBuilder.createResponse());
return Rec(ResponseBuilder().createResponse());
}
if (ReqUID == RequestDemangle) { if (ReqUID == RequestDemangle) {
SmallVector<const char *, 8> MangledNames; SmallVector<const char *, 8> MangledNames;
bool Failed = Req.getStringArray(KeyNames, MangledNames, /*isOptional=*/true); bool Failed = Req.getStringArray(KeyNames, MangledNames, /*isOptional=*/true);

View File

@@ -207,6 +207,7 @@ UID_REQUESTS = [
REQUEST('SemanticRefactoring', 'source.request.semantic.refactoring'), REQUEST('SemanticRefactoring', 'source.request.semantic.refactoring'),
REQUEST('EnableCompileNotifications', REQUEST('EnableCompileNotifications',
'source.request.enable-compile-notifications'), 'source.request.enable-compile-notifications'),
REQUEST('TestNotification', 'source.request.test_notification'),
] ]