blob: 41a82b719f67e598ec671c33029ab2ff56e46d16 [file] [log] [blame]
//===--- sourcekitd.cpp ---------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "sourcekitd/Internal-XPC.h"
#include "SourceKit/Support/Logging.h"
#include "SourceKit/Support/Tracing.h"
#include "SourceKit/Support/UIdent.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Mutex.h"
#include <chrono>
#include <xpc/xpc.h>
#include <dispatch/dispatch.h>
#include <Block.h>
using namespace SourceKit;
using namespace sourcekitd;
static UIdent gKeyNotification("key.notification");
static UIdent gKeyDuration("key.duration");
static UIdent gSemaDisableNotificationUID("source.notification.sema_disabled");
void handleTraceMessageRequest(uint64_t Session, xpc_object_t Msg);
void initializeTracing();
void persistTracingData();
bool isTracingEnabled();
static llvm::sys::Mutex GlobalHandlersMtx;
static sourcekitd_uid_handler_t UidMappingHandler;
static sourcekitd_str_from_uid_handler_t StrFromUidMappingHandler;
void
sourcekitd_set_uid_handler(sourcekitd_uid_handler_t handler) {
llvm::sys::ScopedLock L(GlobalHandlersMtx);
sourcekitd_uid_handler_t newHandler = Block_copy(handler);
Block_release(UidMappingHandler);
UidMappingHandler = newHandler;
}
void
sourcekitd_set_uid_handlers(sourcekitd_uid_from_str_handler_t uid_from_str,
sourcekitd_str_from_uid_handler_t str_from_uid) {
llvm::sys::ScopedLock L(GlobalHandlersMtx);
sourcekitd_uid_handler_t newUIDFromStrHandler = Block_copy(uid_from_str);
Block_release(UidMappingHandler);
UidMappingHandler = newUIDFromStrHandler;
sourcekitd_str_from_uid_handler_t newStrFromUIDHandler = Block_copy(str_from_uid);
Block_release(StrFromUidMappingHandler);
StrFromUidMappingHandler = newStrFromUIDHandler;
}
sourcekitd_uid_t sourcekitd::SKDUIDFromUIdent(UIdent UID) {
if (void *Tag = UID.getTag())
return reinterpret_cast<sourcekitd_uid_t>(Tag);
if (UidMappingHandler) {
sourcekitd_uid_t skduid = UidMappingHandler(UID.c_str());
if (skduid) {
UID.setTag(skduid);
return skduid;
}
}
return reinterpret_cast<sourcekitd_uid_t>(UID.getAsOpaqueValue());
}
UIdent sourcekitd::UIdentFromSKDUID(sourcekitd_uid_t uid) {
if (StrFromUidMappingHandler) {
if (const char *str = StrFromUidMappingHandler(uid))
return UIdent(str);
}
return UIdent::getFromOpaqueValue(uid);
}
static xpc_connection_t GlobalConn = nullptr;
static sourcekitd_interrupted_connection_handler_t InterruptedConnectionHandler;
static sourcekitd_response_receiver_t NotificationReceiver;
void
sourcekitd_set_interrupted_connection_handler(
sourcekitd_interrupted_connection_handler_t handler) {
sourcekitd_interrupted_connection_handler_t newHandler = Block_copy(handler);
dispatch_async(dispatch_get_main_queue(), ^{
Block_release(InterruptedConnectionHandler);
InterruptedConnectionHandler = newHandler;
});
}
void
sourcekitd_set_notification_handler(sourcekitd_response_receiver_t receiver) {
sourcekitd_response_receiver_t newReceiver = Block_copy(receiver);
dispatch_async(dispatch_get_main_queue(), ^{
Block_release(NotificationReceiver);
NotificationReceiver = newReceiver;
});
}
//===----------------------------------------------------------------------===//
// sourcekitd_request_sync
//===----------------------------------------------------------------------===//
static xpc_connection_t getGlobalConnection();
static bool ConnectionInterrupted = false;
sourcekitd_response_t sourcekitd_send_request_sync(sourcekitd_object_t req) {
LOG_SECTION("sourcekitd_send_request_sync-before", InfoHighPrio) {
// Requests will be printed in Requests.cpp, print them out here as well.
sourcekitd::printRequestObject(req, Log->getOS());
}
if (ConnectionInterrupted) {
LOG_WARN_FUNC("request dropped while restoring service");
return sourcekitd::createErrorRequestInterrupted("restoring service");
}
xpc_connection_t Conn = getGlobalConnection();
xpc_object_t contents = xpc_array_create(nullptr, 0);
xpc_array_append_value(contents, req);
xpc_object_t msg = xpc_dictionary_create(nullptr, nullptr, 0);
xpc_dictionary_set_value(msg, "msg", contents);
xpc_release(contents);
xpc_object_t reply = xpc_connection_send_message_with_reply_sync(Conn, msg);
xpc_release(msg);
if (xpc_get_type(reply) == XPC_TYPE_ERROR)
return reply;
assert(xpc_get_type(reply) == XPC_TYPE_DICTIONARY);
sourcekitd_object_t resp =
xpc_dictionary_get_value(reply, xpc::KeyMsgResponse);
xpc_retain(resp);
xpc_release(reply);
LOG_SECTION("sourcekitd_send_request_sync-after", InfoHighPrio) {
// Responses will be printed in Requests.cpp (with medium prio), print them
// out here as well.
if (Logger::isLoggingEnabledForLevel(Logger::Level::InfoMediumPrio))
sourcekitd::printResponse(resp, Log->getOS());
}
return resp;
}
void sourcekitd_send_request(sourcekitd_object_t req,
sourcekitd_request_handle_t *out_handle,
sourcekitd_response_receiver_t receiver) {
// FIXME: Implement request handle.
LOG_SECTION("sourcekitd_send_request-before", InfoHighPrio) {
// Requests will be printed in Requests.cpp, print them out here as well.
sourcekitd::printRequestObject(req, Log->getOS());
}
if (ConnectionInterrupted) {
LOG_WARN_FUNC("request dropped while restoring service");
receiver(sourcekitd::createErrorRequestInterrupted("restoring service"));
return;
}
xpc_connection_t Conn = getGlobalConnection();
xpc_object_t contents = xpc_array_create(nullptr, 0);
xpc_array_append_value(contents, req);
xpc_object_t msg = xpc_dictionary_create(nullptr, nullptr, 0);
xpc_dictionary_set_value(msg, "msg", contents);
xpc_release(contents);
dispatch_queue_t queue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
xpc_connection_send_message_with_reply(Conn, msg, queue,
^(xpc_object_t reply) {
sourcekitd_response_t Resp = nullptr;
if (xpc_get_type(reply) == XPC_TYPE_ERROR) {
Resp = reply;
} else {
assert(xpc_get_type(reply) == XPC_TYPE_DICTIONARY);
Resp = xpc_dictionary_get_value(reply, xpc::KeyMsgResponse);
}
// In sourcekitd, the receiver accepts ownership of the response.
xpc_retain(Resp);
LOG_SECTION("sourcekitd_send_request-after", InfoHighPrio) {
// Responses will be printed in Requests.cpp (with medium prio),
// print them out here as well.
if (Logger::isLoggingEnabledForLevel(Logger::Level::InfoMediumPrio))
sourcekitd::printResponse(Resp, Log->getOS());
}
receiver(Resp);
});
xpc_release(msg);
}
void sourcekitd_cancel_request(sourcekitd_request_handle_t handle) {
// FIXME: Implement cancelling.
}
/// To avoid repeated crashes, used to notify the service to delay typechecking
/// in the editor for a certain amount of seconds.
static std::atomic<size_t> SemanticEditorDelaySecondsNum;
static void handleInternalInitRequest(xpc_object_t reply) {
size_t Delay = SemanticEditorDelaySecondsNum;
if (Delay != 0)
xpc_dictionary_set_uint64(reply, xpc::KeySemaEditorDelay, Delay);
xpc_dictionary_set_uint64(reply, xpc::KeyTracingEnabled, isTracingEnabled());
}
static void handleInternalUIDRequest(xpc_object_t XVal,
xpc_object_t reply) {
xpc_object_t response = nullptr;
if (xpc_get_type(XVal) == XPC_TYPE_STRING) {
const char *Str = xpc_string_get_string_ptr(XVal);
LOG_INFO_FUNC(Low, "service queried UID for: " << Str);
sourcekitd_uid_t SKDUID = sourcekitd_uid_get_from_cstr(Str);
response = xpc_uint64_create(uintptr_t(SKDUID));
} else if (xpc_get_type(XVal) == XPC_TYPE_UINT64) {
uint64_t Val = xpc_uint64_get_value(XVal);
sourcekitd_uid_t SKDUID = reinterpret_cast<sourcekitd_uid_t>(Val);
const char *Str = sourcekitd_uid_get_string_ptr(SKDUID);
LOG_INFO_FUNC(Low, "service queried string of UID: " << Str);
response = xpc_string_create(Str);
} else {
llvm::report_fatal_error("Unknown internal message");
}
xpc_dictionary_set_value(reply, xpc::KeyMsgResponse, response);
xpc_release(response);
}
static void handleInterruptedConnection(xpc_object_t event, xpc_connection_t conn);
void sourcekitd::initialize() {
initializeTracing();
assert(!GlobalConn);
GlobalConn = xpc_connection_create(SOURCEKIT_XPCSERVICE_IDENTIFIER, nullptr);
xpc_connection_set_event_handler(GlobalConn, ^(xpc_object_t event) {
xpc_type_t type = xpc_get_type(event);
if (type == XPC_TYPE_ERROR) {
if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
// The service has crashed. The XPC connection is still valid and
// sending a message to it will re-launch the service.
LOG_WARN("connection-event-handler", "Connection interrupt");
handleInterruptedConnection(event, GlobalConn);
} else if (event == XPC_ERROR_CONNECTION_INVALID) {
// Client initiated shutdown.
LOG_INFO("connection-event-handler", High,
"connection invalid error (shutdown)");
xpc_release(GlobalConn);
GlobalConn = nullptr;
} else {
LOG_WARN("connection-event-handler",
"Received unexpected error event: " <<
xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
}
} else {
xpc_object_t contents = xpc_dictionary_get_value(event, xpc::KeyInternalMsg);
if (!contents) {
llvm::report_fatal_error("Received unexpected message from service");
}
xpc::Message msg = (xpc::Message)xpc_array_get_uint64(contents, 0);
switch (msg) {
case xpc::Message::Initialization: {
xpc_object_t reply = xpc_dictionary_create_reply(event);
handleInternalInitRequest(reply);
xpc_connection_send_message(GlobalConn, reply);
xpc_release(reply);
break;
}
case xpc::Message::Notification: {
xpc_object_t notif_contents = xpc_retain(xpc_array_get_value(contents, 1));
dispatch_async(dispatch_get_main_queue(), ^{
if (NotificationReceiver != nullptr) {
// The receiver accepts ownership of the notification object.
NotificationReceiver(xpc_retain(notif_contents));
}
xpc_release(notif_contents);
});
break;
}
case xpc::Message::UIDSynchronization: {
xpc_object_t reply = xpc_dictionary_create_reply(event);
handleInternalUIDRequest(xpc_array_get_value(contents, 1), reply);
xpc_connection_send_message(GlobalConn, reply);
xpc_release(reply);
break;
}
case xpc::Message::TraceMessage: {
xpc_object_t reply = xpc_dictionary_create_reply(event);
handleTraceMessageRequest(xpc_array_get_uint64(contents, 1),
xpc_array_get_value(contents, 2));
xpc_connection_send_message(GlobalConn, reply);
xpc_release(reply);
break;
}
}
}
});
xpc_connection_resume(GlobalConn);
}
void sourcekitd::shutdown() {
assert(GlobalConn);
xpc_connection_cancel(GlobalConn);
}
static xpc_connection_t getGlobalConnection() {
assert(GlobalConn);
return GlobalConn;
}
/// Receives a +1 reference of the connection.
static void pingService(xpc_connection_t ping_conn) {
LOG_WARN_FUNC("pinging service");
xpc_object_t ping_msg = xpc_dictionary_create(nullptr, nullptr, 0);
xpc_dictionary_set_bool(ping_msg, "ping", true);
dispatch_queue_t queue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
xpc_connection_send_message_with_reply(ping_conn, ping_msg, queue,
^(xpc_object_t reply) {
xpc_type_t type = xpc_get_type(reply);
if (type == XPC_TYPE_ERROR) {
if (reply == XPC_ERROR_CONNECTION_INTERRUPTED) {
// Try again.
pingService(ping_conn);
} else if (reply == XPC_ERROR_CONNECTION_INVALID) {
// Client initiated shutdown.
LOG_WARN("ping-event-handler", "connection invalid error");
xpc_release(ping_conn);
} else {
LOG_WARN("ping-event-handler",
"Received unexpected error reply: " <<
xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION));
// Try again.
pingService(ping_conn);
}
return;
}
LOG_WARN("ping-event-handler", "service restored");
ConnectionInterrupted = false;
// Create an empty response as notification that the service is restored.
sourcekitd::ResponseBuilder RespBuilder;
xpc_object_t contents = RespBuilder.createResponse();
dispatch_async(dispatch_get_main_queue(), ^{
if (NotificationReceiver != nullptr) {
// The receiver accepts ownership of the notification object.
xpc_object_t notif_contents = xpc_retain(contents);
NotificationReceiver(notif_contents);
}
xpc_release(contents);
});
xpc_release(ping_conn);
});
xpc_release(ping_msg);
}
static void sendNotification(xpc_object_t event) {
event = xpc_retain(event);
dispatch_async(dispatch_get_main_queue(), ^{
if (NotificationReceiver != nullptr) {
// The receiver accepts ownership of the notification object.
xpc_object_t err_event = xpc_retain(event);
NotificationReceiver(err_event);
}
xpc_release(event);
});
}
static void updateSemanticEditorDelay() {
using namespace std::chrono;
using TimePoint = time_point<system_clock, nanoseconds>;
// Clear any previous setting.
SemanticEditorDelaySecondsNum = 0;
static TimePoint gPrevCrashTime;
TimePoint PrevTime = gPrevCrashTime;
TimePoint CurrTime = system_clock::now();
gPrevCrashTime = CurrTime;
if (PrevTime == TimePoint()) {
// First time that it crashed.
return;
}
auto Diff = duration_cast<seconds>(CurrTime - PrevTime);
if (Diff.count() > 30)
return; // treat this as more likely unrelated to the previous crash.
size_t Delay = std::min(size_t(20), size_t(Diff.count()*2 + 1));
LOG_WARN_FUNC("disabling semantic editor for " << Delay << " seconds");
SemanticEditorDelaySecondsNum = Delay;
// Notify the client that semantic functionality is disabled.
ResponseBuilder RespBuilder;
auto Dict = RespBuilder.getDictionary();
Dict.set(gKeyNotification, gSemaDisableNotificationUID);
Dict.set(gKeyDuration, Delay);
sourcekitd_response_t SemaDisableNotification = RespBuilder.createResponse();
sendNotification(SemaDisableNotification);
sourcekitd_response_dispose(SemaDisableNotification);
}
static void handleInterruptedConnection(xpc_object_t event, xpc_connection_t conn) {
ConnectionInterrupted = true;
persistTracingData();
updateSemanticEditorDelay();
// FIXME: InterruptedConnectionHandler will go away.
dispatch_async(dispatch_get_main_queue(), ^{
if (InterruptedConnectionHandler != nullptr) {
InterruptedConnectionHandler();
}
});
// Send the disconnect error as notification.
sendNotification(event);
// Retain connection while we try to ping it.
// Since this happens implicitly, we can't blame the client if it shuts down
// while we are trying to ping.
pingService((xpc_connection_t)xpc_retain(conn));
}