From 5a40064826d30a80ee3d120bfe066424d44bb060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 18 Jul 2018 10:03:54 +0300 Subject: [PATCH] MXS-1929: Make services destroyable Services can now be destroyed if they have no active listeners and they are not linked to servers. When these conditions are met, the service will be destroyed when the last session for the service is closed. The closing of a service will close all listeners that were once assigned to the service. This allows closing of the ports at runtime which previously was done only on shutdown. Exposed the command through the REST API but not through MaxAdmin as it is deprecated. --- include/maxscale/service.h | 1 + server/core/config_runtime.cc | 24 +++++++++- server/core/internal/config_runtime.h | 11 +++++ server/core/internal/service.h | 21 ++++++++ server/core/resource.cc | 15 ++++++ server/core/service.cc | 69 ++++++++++++++++++++++++++- server/core/session.cc | 26 +++++++++- 7 files changed, 163 insertions(+), 4 deletions(-) diff --git a/include/maxscale/service.h b/include/maxscale/service.h index e46808ad1..6381b47fb 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -157,6 +157,7 @@ typedef struct service uint64_t capabilities; /**< The capabilities of the service, @see enum routing_capability */ int max_retry_interval; /**< Maximum retry interval */ bool session_track_trx_state; /**< Get transaction state via session track mechanism */ + bool active; /**< Whether the service is still active */ } SERVICE; typedef enum count_spec_t diff --git a/server/core/config_runtime.cc b/server/core/config_runtime.cc index c6b7c62e6..aacb7e563 100644 --- a/server/core/config_runtime.cc +++ b/server/core/config_runtime.cc @@ -979,8 +979,8 @@ bool runtime_destroy_listener(SERVICE *service, const char *name) { rval = true; MXS_NOTICE("Destroyed listener '%s' for service '%s'. The listener " - "will be removed after the next restart of MaxScale.", - name, service->name); + "will be removed after the next restart of MaxScale or " + "when the associated service is destroyed.", name, service->name); } return rval; @@ -1078,6 +1078,26 @@ bool runtime_create_filter(const char *name, const char *module, MXS_CONFIG_PARA return rval; } +bool runtime_destroy_service(SERVICE* service) +{ + bool rval = false; + mxs::SpinLockGuard guard(crt_lock); + ss_dassert(service && service->active); + + if (service_can_be_destroyed(service)) + { + service_destroy(service); + rval = true; + } + else + { + runtime_error("Service '%s' cannot be destroyed: Remove all servers and " + "destroy all listeners first", service->name); + } + + return rval; +} + bool runtime_destroy_monitor(MXS_MONITOR *monitor) { bool rval = false; diff --git a/server/core/internal/config_runtime.h b/server/core/internal/config_runtime.h index 9e4f9d133..d4ab6fd62 100644 --- a/server/core/internal/config_runtime.h +++ b/server/core/internal/config_runtime.h @@ -216,6 +216,17 @@ bool runtime_create_filter(const char *name, const char *module, MXS_CONFIG_PARA */ bool runtime_destroy_monitor(MXS_MONITOR *monitor); +/** + * Destroy a service + * + * The service can only be destroyed if it uses no servers and has no active listeners. + * + * @param service Service to destroy + * + * @return True if service was destroyed + */ +bool runtime_destroy_service(SERVICE* service); + /** * @brief Create a new server from JSON * diff --git a/server/core/internal/service.h b/server/core/internal/service.h index 508042417..87ce7ef6c 100644 --- a/server/core/internal/service.h +++ b/server/core/internal/service.h @@ -40,10 +40,31 @@ SERVICE* service_alloc(const char *name, const char *router, MXS_CONFIG_PARAMETE /** * Free a service * + * @note Must not be called if the service has any active client connections or + * active listeners + * * @param service Service to free */ void service_free(SERVICE* service); +/** + * Mark a service for destruction + * + * Once the service reference count drops down to zero, the service is destroyed. + * + * @param service Service to destroy + */ +void service_destroy(SERVICE *service); + +/** + * Check whether a service can be destroyed + * + * @param service Service to check + * + * @return True if service can be destroyed + */ +bool service_can_be_destroyed(SERVICE *service); + /** * @brief Shut all services down * diff --git a/server/core/resource.cc b/server/core/resource.cc index ca801e51f..135615ca7 100644 --- a/server/core/resource.cc +++ b/server/core/resource.cc @@ -459,6 +459,19 @@ HttpResponse cb_delete_listener(const HttpRequest& request) return HttpResponse(MHD_HTTP_NO_CONTENT); } +HttpResponse cb_delete_service(const HttpRequest& request) +{ + SERVICE* service = service_find(request.uri_part(1).c_str()); + ss_dassert(service); + + if (runtime_destroy_service(service)) + { + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); +} + HttpResponse cb_all_servers(const HttpRequest& request) { return HttpResponse(MHD_HTTP_OK, server_list_to_json(request.host())); @@ -930,6 +943,8 @@ public: m_delete.push_back(SResource(new Resource(cb_delete_server, 2, "servers", ":server"))); m_delete.push_back(SResource(new Resource(cb_delete_monitor, 2, "monitors", ":monitor"))); + m_delete.push_back(SResource(new Resource(cb_delete_service, 2, "services", ":service"))); + m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "inet", ":inetuser"))); m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "unix", ":unixuser"))); diff --git a/server/core/service.cc b/server/core/service.cc index 81801e2eb..02afd04ff 100644 --- a/server/core/service.cc +++ b/server/core/service.cc @@ -58,6 +58,7 @@ #include "internal/filter.h" #include "internal/modules.h" #include "internal/service.h" +#include "internal/routingworker.hh" /** This define is needed in CentOS 6 systems */ #if !defined(UINT64_MAX) @@ -114,6 +115,7 @@ SERVICE* service_alloc(const char *name, const char *router, MXS_CONFIG_PARAMETE service->stats.started = time(0); service->stats.n_failed_starts = 0; service->state = SERVICE_STATE_ALLOC; + service->active = true; spinlock_init(&service->spin); service->max_retry_interval = config_get_integer(params, CN_MAX_RETRY_INTERVAL); @@ -183,6 +185,7 @@ SERVICE* service_alloc(const char *name, const char *router, MXS_CONFIG_PARAMETE void service_free(SERVICE* service) { ss_dassert(atomic_load_int(&service->client_count) == 0); + ss_dassert(!service->active); spinlock_acquire(&service_spin); @@ -208,6 +211,7 @@ void service_free(SERVICE* service) { auto tmp = service->ports; service->ports = service->ports->next; + ss_dassert(!tmp->active); listener_free(tmp); } @@ -219,6 +223,7 @@ void service_free(SERVICE* service) while (service->dbref) { SERVER_REF* tmp = service->dbref; + ss_dassert(!tmp->active); service->dbref = service->dbref->next; MXS_FREE(tmp); } @@ -231,6 +236,34 @@ void service_free(SERVICE* service) MXS_FREE(service); } +void service_destroy(SERVICE* service) +{ +#ifdef SS_DEBUG + auto current = mxs::RoutingWorker::get_current(); + auto main = mxs::RoutingWorker::get(mxs::RoutingWorker::MAIN); + ss_info_dassert(current == main, "Destruction of service must be done on the main worker"); +#endif + + ss_dassert(service->active); + service->active = false; + + char filename[PATH_MAX + 1]; + snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), + service->name); + + if (unlink(filename) == -1 && errno != ENOENT) + { + MXS_ERROR("Failed to remove persisted service configuration at '%s': %d, %s", + filename, errno, mxs_strerror(errno)); + } + + if (atomic_load_int(&service->client_count) == 0) + { + // The service has no active sessions, it can be closed immediately + service_free(service); + } +} + /** * Check to see if a service pointer is valid * @@ -841,6 +874,36 @@ bool service_has_named_listener(SERVICE *service, const char *name) return false; } +bool service_can_be_destroyed(SERVICE *service) +{ + bool rval = true; + LISTENER_ITERATOR iter; + + for (SERV_LISTENER *listener = listener_iterator_init(service, &iter); + listener; listener = listener_iterator_next(&iter)) + { + if (listener_is_active(listener)) + { + rval = false; + break; + } + } + + if (rval) + { + for (auto s = service->dbref; s; s = s->next) + { + if (s->active) + { + rval = false; + break; + } + } + } + + return rval; +} + /** * Allocate a new server reference * @@ -1238,8 +1301,12 @@ service_find(const char *servname) spinlock_acquire(&service_spin); service = allServices; - while (service && strcmp(service->name, servname) != 0) + while (service) { + if (strcmp(service->name, servname) == 0 && service->active) + { + break; + } service = service->next; } spinlock_release(&service_spin); diff --git a/server/core/session.cc b/server/core/session.cc index a16d25618..d9d110df0 100644 --- a/server/core/session.cc +++ b/server/core/session.cc @@ -44,6 +44,7 @@ #include "internal/filter.h" #include "internal/routingworker.hh" #include "internal/session.h" +#include "internal/service.h" using std::string; using std::stringstream; @@ -341,6 +342,23 @@ void session_close(MXS_SESSION *session) } } +class ServiceDestroyTask: public mxs::WorkerDisposableTask +{ +public: + ServiceDestroyTask(SERVICE* service): + m_service(service) + { + } + + void execute(Worker& worker) override + { + service_free(m_service); + } + +private: + SERVICE* m_service; +}; + /** * Deallocate the specified session * @@ -395,7 +413,13 @@ static void session_free(MXS_SESSION *session) session->state = SESSION_STATE_FREE; session_final_free(session); - atomic_add(&service->client_count, -1); + + if (atomic_add(&service->client_count, -1) == 1 && !service->active) + { + // Destroy the service in the main routing worker thread + mxs::RoutingWorker* main_worker = mxs::RoutingWorker::get(mxs::RoutingWorker::MAIN); + main_worker->post(std::auto_ptr(new ServiceDestroyTask(service))); + } } static void