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