diff --git a/CMakeLists.txt b/CMakeLists.txt index c97c221a1..8cf502b42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,9 @@ add_subdirectory(include/maxscale) if(NOT WITHOUT_MAXADMIN) add_subdirectory(client) endif() +if(BUILD_TESTS) + add_subdirectory(examples) +endif() # Generate text versions of some documents execute_process(COMMAND perl ${CMAKE_SOURCE_DIR}/Documentation/format.pl @@ -224,6 +227,9 @@ install_file(etc/lsyncd_example.conf core) install_manual(Documentation/maxscale.1 1 core) install_file(${CMAKE_SOURCE_DIR}/server/maxscale_binlogserver_template.cnf core) +# For devel package, these files are put to the base folder +install_header(${CMAKE_SOURCE_DIR}/COPYRIGHT devel) +install_header(${CMAKE_SOURCE_DIR}/LICENSE.TXT devel) # Install startup scripts and ldconfig files if(WITH_SCRIPTS) diff --git a/Documentation/Design-Documents/Plugin-development-guide.md b/Documentation/Design-Documents/Plugin-development-guide.md new file mode 100644 index 000000000..3677ca3a9 --- /dev/null +++ b/Documentation/Design-Documents/Plugin-development-guide.md @@ -0,0 +1,566 @@ +# MariaDB MaxScale plugin development guide + +This document and the attached example code explain prospective plugin +developers the MariaDB MaxScale plugin API and also present and explain some +best practices and possible pitfalls in module development. We predict that +*filters* and *routers* are the module types developers are most likely to work +on, so the APIs of these two are discussed in detail. + +## Table of Contents + +1. [Introduction](#introduction) +* [Module categories](#module-categories) +* [Common definitions and headers](#common-definitions-and-headers) + 1. [Module information container](#module-information-container) +* [Module API](#module-api) + 1. [Overview](#overview) + * [General module management](#general-module-management) + * [Protocol](#protocol) + * [Authenticator](#authenticator) + * [Filter and Router](#filter-and-router) + * [Monitor](#monitor) +* [Compiling, installing and running](#compiling-installing-and-running) + 1. [Hands-on example: RoundRobinRouter](#hands-on-example-roundrobinrouter) +* [Summary and conclusion](#summary-and-conclusion) + +## Introduction + +MariaDB MaxScale is designed to be an extensible program. Much, if not most, of +the actual processing is done by plugin modules. Plugins receive network data, +process it and relay it to its destination. The MaxScale core loads plugins, +manages client sessions and threads and, most importantly, offers a selection of +functions for the plugins to call upon. This collection of functions is called +the *MaxScale Public Interface* or just **MPI** for short. + +The plugin modules are shared libraries (.so-files) implementing a set of +interface functions, the plugin API. Different plugin types have different APIs, +although there are similarities. The MPI is a set of C and C++ header files, +from which the module code includes the ones required. MariaDB MaxScale is +written in C/C++ and the plugin API is in pure C. Although it is possible +to write plugins in any language capable of exposing a C interface and +dynamically binding to the core program, in this document we assume plugin +modules are written in C++. + +The *RoundRobinRouter* is a practical example of a simple router plugin. The +RoundRobinRouter is compiled, installed and ran in [section +5.1](#hands-on-example:-roundrobinrouter). The source for the router is located +in the `examples`-folder. + +## Module categories + +This section lists all the module types and summarises their core tasks. The +modules are listed in the order a client packet would typically travel through. +For more information about a particular module type, see the corresponding +folder in `MaxScale/Documentation/`, located in the main MariaDB MaxScale +repository. + +**Protocol** modules implement I/O between clients and MaxScale, and between +MaxScale and backend servers. Protocol modules read and write to socket +descriptors using raw I/O functions provided by the MPI, and implement +protocol-specific I/O functions to be used through a common interface. The +Protocol module API is defined in `protocol.h`. Currently, the only implemented +database protocol is *MySQL*. Other protocols currently in use include *HTTPD* +and *maxscaled*, which are used by the MaxInfo and MaxAdmin modules. + +**Authenticator** modules retrieve user account information from the backend +databases, store it and use it to authenticate connecting clients. MariaDB +MaxScale includes authenticators for MySQL (normal and GSSApi). The +authenticator API is defined in `authenticator.h`. + +**Filter** modules process data from clients before routing. A data buffer may +travel through multiple filters before arriving in a router. For a data buffer +going from a backend to the client, the router receives it first and the +filters receive it in reverse order. MaxScale includes a healthly selection of +filters ranging from logging, overwriting query data and caching. The filter +API is defined in `filter.h`. + +**Router** modules route packets from the last filter in the filter chain to +backends and reply data from backends to the last filter. The routing decisions +may be based on a variety of conditions; typically packet contents and backend +status are the most significant factors. Routers are often used for load +balancing, dividing clients and even individual queries between backends. +Routers use protocol functions to write to backends, making them somewhat +protocol-agnostic. The router API is defined in `router.h`. + +**Monitor** modules do not process data flowing through MariaDB MaxScale, but +support the other modules in their operation by updating the status of the +backend servers. Monitors are ran in their own threads to minimize +interference to the worker threads. They periodically connect to all their +assigned backends, query their status and write the results in global structs. +The monitor API is defined in `monitor.h`. + +## Common definitions and headers + +Generally, most type definitions, macros and functions exposed by the MPI to be +used by modules are prefixed with **MXS**. This should avoid name collisions +in the case a module includes many symbols from the MPI. + +Every compilation unit in a module should begin with `#define MXS_MODULE_NAME +""`. This definition will be used by log macros for clarity, prepending +`` to every log message. Next, the module should +`#include ` (for C++) or `#include ` (for +C). These headers contain compilation environment dependent definitions and +global constants, and include some generally useful headers. Including one of +them first in every source file enables later global redefinitions across all +MaxScale modules. If your module is composed of multiple source files, the above +should be placed to a common header file included in the beginning of the source +files. The file with the module API definition should also include the header +for the module type, e.g. `filter.h`. + +Other common MPI header files required by most modules are listed in the table +below. + +Header | Contents +-------|---------- +`alloc.h` | Malloc, calloc etc. replacements +`buffer.h` | Packet buffer management +`config.h` | Configuration settings +`dcb.h` | I/O using descriptor control blocks +`debug.h` | Debugging macros +`modinfo.h` | Module information structure +`server.h` | Backend server information +`service.h` | Service definition +`session.h` | Client session definition +`logmanager.h` | Logging macros and functions + +### Module information container + +A module must implement the `MXS_CREATE_MODULE()`-function, which returns a +pointer to a `MXS_MODULE`-structure. This function is called by the module +loader during program startup. `MXS_MODULE` (type defined in `modinfo.h`) +contains function pointers to further module entrypoints, miscellaneous +information about the module and the configuration parameters accepted by the +module. This function must be exported without C++ name mangling, so in C++ code +it should be defined `extern "C"`. + +The information container describes the module in general and is constructed +once during program excecution. A module may have multiple *instances* with +different values for configuration parameters. For example, a filter module can +be used with two different configurations in different services (or even in the +same service). In this case the loader uses the same module information +container for both but creates two module instances. + +The MariaDB MaxScale configuration file `maxscale.cnf` is parsed by the core. +The core also checks that all the defined parameters are of the correct type for +the module. For this, the `MXS_MODULE`-structure includes a list of parameters +accepted by the module, defining parameter names, types and default values. In +the actual module code, parameter values should be extracted using functions +defined in `config.h`. + +## Module API + +### Overview + +This section explains some general concepts encountered when implementing a +module API. For more detailed information, see the module specific subsection, +header files or the doxygen documentation. + +Modules with configuration data define an *INSTANCE* object, which is created by +the module code in a `createInstance`-function or equivalent. The instance +creation function is called during MaxScale startup, usually when creating +services. MaxScale core holds the module instance data in the +`SERVICE`-structure (or other higher level construct) and gives it as a +parameter when calling functions from the module in question. The instance +structure should contain all non-client-specific information required by the +functions of the module. The core does not know what the object contains (since +it is defined by the module itself), nor will it modify the pointer or the +referenced object in any way. + +Modules dealing with client-specific data require a *SESSION* object for every +client. As with the instance data, the definition of the module session +structure is up to the module writer and MaxScale treats it as an opaque type. +Usually the session contains status indicators and any resources required by the +client. MaxScale core has its own `MXS_SESSION` object, which tracks a variety +of client related information. The `MXS_SESSION` is given as a parameter to +module-specific session creation functions and is required for several typical +operations such as connecting to backends. + +Descriptor control blocks (`DCB`), are generalized I/O descriptor types. DCBs +store the file descriptor, state, remote address, username, session, and other +data. DCBs are created whenever a new socket is created. Typically this happens +when a new client connects or MaxScale connects the client session to backend +servers. The module writer should use DCB handling functions provided by the MPI +to manage connections instead of calling general networking libraries. This +ensures that I/O is handled asynchronously by epoll. In general, module code +should avoid blocking I/O, *sleep*, *yield* or other potentially costly +operations, as the same thread is typically used for many client sessions. + +Network data such as client queries and backend replies are held in a buffer +container called `GWBUF`. Multiple GWBUFs can form a linked list with type +information and properties in each GWBUF-node. Each node includes a pointer to a +reference counted shared buffer (`SHARED_BUF`), which finally points to a slice +of the actual data. In effect, multiple GWBUF-chains can share some data while +keeping some parts private. This construction is meant to minimize the need for +data copying and makes it easy to append more data to partially received data +packets. Plugin writers should use the MPI to manipulate GWBUFs. For more +information on the GWBUF, see [Filter and Router](#filter-and-router). + +### General module management + +```java +int process_init() +void process_finish() +int thread_init() +void thread_finish() +``` + +These four functions are present in all `MXS_MODULE` structs and are not part of +the API of any individual module type. `process_init` and `process_finish` are +called by the module loader right after loading a module and just before +MaxScale terminates, respectively. Usually, these can be set to null in +`MXS_MODULE` unless the module needs some general initializations before +creating any instances. `thread_init` and `thread_finish` are thread-specific +equivalents. + +```java +void diagnostics(INSTANCE *instance, DCB *dcb) +``` + +A diagnostics printing routine is present in nearly all module types, although +with varying signatures. This entrypoint should print various statistics and +status information about the module instance `instance` in string form. The +target of the printing is the given DCB, and printing should be implemented by +calling `dcb_printf`. The diagnostics function is used by the *MaxInfo* and +*MaxAdmin* features. + +### Protocol + +```java +int32_t read(struct dcb *) +int32_t write(struct dcb *, GWBUF *) +int32_t write_ready(struct dcb *) +int32_t error(struct dcb *) +int32_t hangup(struct dcb *) +int32_t accept(struct dcb *) +int32_t connect(struct dcb *, struct server *, struct session *) +int32_t close(struct dcb *) +int32_t listen(struct dcb *, char *) +int32_t auth(struct dcb *, struct server *, struct session *, GWBUF *) +int32_t session(struct dcb *, void *) +char auth_default() +int32_t connlimit(struct dcb *, int limit) +``` + +Protocol modules are laborous to implement due to their low level nature. Each +DCB maintains pointers to the correct protocol functions to be used with it, +allowing the DCB to be used in a protocol-independent manner. + +`read`, `write_ready`, `error` and `hangup` are *epoll* handlers for their +respective events. `write` implements writing and is usually called in a router +module. `accept` is a listener socker handler. `connect` is used during session +creation when connecting to backend servers. `listen` creates a listener socket. +`close` closes a DCB created by `accept`, `connect` or `listen`. + +In the ideal case modules other than the protocol modules themselves should not +be protocol-specific. This is currently difficult to achieve, since many actions +in the modules are dependent on protocol-speficic details. In the future, +protocol modules may be expanded to implement a generic query parsing and +information API, allowing filters and routers to be used with different SQL +variants. + +### Authenticator + +```java +void* initialize(char **options) +void* create(void* instance) +int extract(struct dcb *, GWBUF *) +bool connectssl(struct dcb *) +int authenticate(struct dcb *) +void free(struct dcb *) +void destroy(void *) +int loadusers(struct servlistener *) +void diagnostic(struct dcb*, struct servlistener *) +int reauthenticate(struct dcb *, const char *user, uint8_t *token, + size_t token_len, uint8_t *scramble, size_t scramble_len, + uint8_t *output, size_t output_len); +``` + +Authenticators must communicate with the client or the backends and implement +authentication. The authenticators can be divided to client and backend modules, +although the two types are linked and must be used together. Authenticators are +also dependent on the protocol modules. + +### Filter and Router + +Filter and router APIs are nearly identical and are presented together. Since +these are the modules most likely to be implemented by plugin developers, their +APIs are discussed in more detail. + +```java +INSTANCE* createInstance(SERVICE* service, char** options) +void destroyInstance(INSTANCE* instance) +``` + +`createInstance` should read the `options` and initialize an instance object for +use with `service`. Often, simply saving the configuration values to fields is +enough. `destroyInstance` is called when the service using the module is +deallocated. It should free any resources claimed by the instance. All sessions +created by this instance should be closed before calling the destructor. + +```java +SESSION* newSession(INSTANCE* instance, MXS_SESSION* mxs_session) +void closeSession(INSTANCE* instance, SESSION* session) +void freeSession(INSTANCE* instance, SESSION* session) +``` + +These functions manage sessions. `newSession` should allocate a router or filter +session attached to the client session represented by `mxs_session`. MaxScale +will pass the returned pointer to all the API entrypoints that process user data +for the particular client. `closeSession` should close connections the session +has opened and release any resources specific to the served client. The +*SESSION* structure allocated in `newSession` should not be deallocated by +`closeSession` but in `freeSession`. These two are called in succession +by the core. + +```java +int routeQuery(INSTANCE *instance, SESSION session, GWBUF* queue) void +clientReply(INSTANCE* instance, SESSION session, GWBUF* queue, DCB *backend_dcb) +uint64_t getCapabilities(INSTANCE* instance) +``` + +`routeQuery` is called for client requests which should be routed to backends, +and `clientReply` for backend reply packets which should be routed to the +client. For some modules, MaxScale itself is the backend. For filters, these can +be NULL, in which case the filter will be skipped for that packet type. + +`routeQuery` is often the most complicated function in a router, as it +implements the routing logic. It typically considers the client request `queue`, +the router settings in `instance` and the session state in `session` when making +a routing decision. For filters aswell, `routeQuery` typically implements the +main logic, although the routing target is constant. For router modules, +`routeQuery` should send data forward with `dcb->func.write()`. Filters should +directly call `routeQuery` for the next filter or router in the chain. + +`clientReply` processes data flowing from backend back to client. For routers, +this function is often much simpler than `routeQuery`, since there is only one +client to route to. Depending on the router, some packets may not be routed to +the client. For example, if a client query was routed to multiple backends, +MaxScale will receive multiple replies while the client only expects one. +Routers should pass the reply packet to the last filter in the chain (reversed +order) using the macro `MXS_SESSION_ROUTE_REPLY`. Filters should call the +`clientReply` of the previous filter in the chain. There is no need for filters +to worry about being the first filter in the chain, as this is handled +transparently by the session creation routine. + +Application data is not always received in complete packets from the network +stack. How partial packets are handled by the receiving protocol module depends +on the attached filters and the router, communicated by their +`getCapabilities`-functions. `getCapabilities` should return a bitfield +resulting from ORring the individual capabilities. `routing.h` lists the allowed +capability flags. + +If a router or filter sets no capabilities, `routeQuery` or `clientReply` may be +called to route partial packets. If the routing logic does not require any +information on the contents of the packets or even tracking the number of +packets, this may be fine. For many cases though, receiving a data packet in a +complete GWBUF chain or in one contiguos GWBUF is required. The former can be +requested by `getCapabilities` returning *RCAP_TYPE_STMT*, the latter by +*RCAP_TYPE_CONTIGUOUS*. Separate settings exist for queries and replies. For +replies, an additional value, *RCAP_TYPE_RESULTSET_OUTPUT* is defined. This +requests the protocol module to gather partial results into one result set. +Enforcing complete packets will delay processing, since the protocol module will +have to wait for the entire data packet to arrive before sending it down the +processing chain. + +```java +void handleError(INSTANCE* instance,SESSION* session, GWBUF* errmsgbuf, + DCB* problem_dcb, mxs_error_action_t action, bool* succp); +``` + +This router-only entrypoint is called if `routeQuery` returns an error value or +if an error occurs in one of the connections listened to by the session. The +steps an error handler typically takes depend on the nature of the `problem_dcb` +and the error encountered. If `problem_dcb` is a client socket, then the session +is lost and should be closed. The error handler should not do this by itself and +just report the failure by setting `succp` to false. If `problem_dcb` is a +backend socket, then the error handler should try to connect to another backend +if the routing logic allows this. If the error is simply a failed authentication +on the backend, then it is usually best to send the message directly to the +client and close the session. + +### Monitor + +```java +MONITOR* startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER *params) +void stopMonitor(MXS_MONITOR *monitor) +void diagnostics(DCB *, const MXS_MONITOR *) +``` + +Monitor modules typically run a repeated monitor routine with a used defined +interval. The `MXS_MONITOR` is a standard monitor definition used for all +monitors and contains a void pointer for storing module specific data. +`startMonitor` should create a new thread for itself using functions in the MPI +and have it regularly run a monitor loop. In the beginning of every monitor +loop, the monitor should lock the `SERVER`-structures of its servers. This +prevents any administrative action from interfering with the monitor during its +pass. + +## Compiling, installing and running + +The requirements for compiling a module are: +* The public headers (MPI) +* A compatible compiler, typically GCC +* Libraries required by the public headers + +Some of the public header files themselves include headers from other libraries. +These libraries need to be installed and it may be required to point out their +location to gcc. Some of the more commonly required libraries are: + * *MySQL Connector-C*, used by the MySQL protocol module + * *pcre2 regular expressions* (libpcre2-dev), used for example by the header +`modutil.h` + +After all dependencies are accounted for, the module should compile with a +command similar to +``` +gcc -I /usr/local/include/mariadb -shared -fPIC -g -o libmymodule.so mymodule.cpp +``` +Large modules composed of several source files and using additional libraries +may require a more complicated compilation scheme, but that is outside the scope +of this document. The result of compiling a plugin should be a single shared +library file. + +The compiled .so-file needs to be copied to the MaxScale library folder, which +is `/usr/local/lib/maxscale` by default. MaxScale expects the filename to be +`lib.so`, where `` must match the module name given in the +configuration file. + +### Hands-on example: RoundRobinRouter + +In this example, the RoundRobinRouter is compiled, installed and tested. The +software environment this section was written and tested is listed below. Any +recent Linux setup should be applicaple. + +* Linux Mint 18 +* gcc 5.4.0, glibc 2.23 +* MariaDB MaxScale 2.1.0 debug build (binaries in `usr/local/maxscale`, modules in +`/usr/local/lib/maxscale`) +* MariaDB Connector-C 2.3.2 (installed to `/usr/local/lib/mariadb`, headers in +`/usr/local/include/mariadb`) +* `roundrobinrouter.cpp` in the current directory +* MaxScale plugin development headers (in `usr/include/maxscale`) + +**Step 1** Compile RoundRobinRouter with `$gcc -I /usr/local/include/mariadb +-shared -fPIC -g -o libroundrobinrouter.so roundrobinrouter.cpp`. +Assuming all headers were found, the shared library `libroundrobinrouter.so` +is produced. + +**Step 2** Copy the compiled module to the MaxScale module directory: `$sudo cp +libroundrobinrouter.so /usr/local/lib/maxscale`. + +**Step 3** Modify the MaxScale configuration file to use the RoundRobinRouter as +a router. Example service and listener definitions are below. The *servers* +and *write_backend*-lines should be configured according to the actual backend +configuration. + +``` +[RR Service] +type=service +router=roundrobinrouter +servers=LocalMaster1,LocalSlave1,LocalSlave2 +user=maxscale +passwd=maxscale +filters=MyLogFilter1 +max_backends=10 +write_backend=LocalMaster1 +print_on_routing=true +dummy_setting=two + +[RR Listener] +type=listener +service=RR Service +protocol=MySQLClient +port=4009 +``` + +**Step 4** Start MaxScale: `$ maxscale -d`. Output: +``` +MariaDB Corporation MaxScale 2.1.0 Mon Feb 20 17:22:18 2017 +------------------------------------------------------ +Info : MaxScale will be run in the terminal process. + See the log from the following log files : + +Configuration file : /etc/maxscale.cnf +Log directory : /var/log/maxscale +Data directory : /var/lib/maxscale +Module directory : /usr/local/lib/maxscale +Service cache : /var/cache/maxscale +``` + +**Step 5** Test with a MySQL client. The RoundRobinRouter has been tested with both a command line and a GUI client. With `DEBUG_RRROUTER` defined and `print_on_routing` enabled, the `/var/log/maxscale/maxscale.log` file will report nearly every action taken by the router. + +``` +2017-02-21 10:37:23 notice : [RoundRobinRouter] Creating instance. +2017-02-21 10:37:23 notice : [RoundRobinRouter] Settings read: +2017-02-21 10:37:23 notice : [RoundRobinRouter] 'max_backends': 10 +2017-02-21 10:37:23 notice : [RoundRobinRouter] 'write_backend': 0xf0ce70 +2017-02-21 10:37:23 notice : [RoundRobinRouter] 'print_on_routing': 1 +2017-02-21 10:37:23 notice : [RoundRobinRouter] 'dummy_setting': 2 +. +. +. +2017-02-21 10:37:37 notice : [RoundRobinRouter] Session with 4 connections created. +2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: SHOW VARIABLES WHERE Variable_name in ('max_allowed_packet', 'system_time_zone', 'time_zone', 'sql_mode') +2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 110u to backend 'LocalMaster1'. +2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client. +2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: set session autocommit=1,sql_mode='NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES' +2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 103u to 4 backends. +2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client. +2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: SET @ApplicationName='DBeaver 3.8.5 - Main' +2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 48u to 4 backends. +2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client. +2017-02-21 10:37:37 notice : [RoundRobinRouter] QUERY: select @@lower_case_table_names +2017-02-21 10:37:37 notice : [RoundRobinRouter] Routing statement of length 36u to backend 'LocalSlave1'. +2017-02-21 10:37:37 notice : [RoundRobinRouter] Replied to client. +``` + +**Step 5** Connect with MaxAdmin, print diagnostics and call a custom command. +``` +$sudo maxadmin +MaxScale> show service "RR Service" + Service: RR Service + Router: roundrobinrouter + State: Started + Queries routed successfully: 37 + Failed routing attempts: 0 + Client replies routed: 38 + Started: Tue Feb 21 11:52:08 2017 + Root user access: Disabled + Filter chain: MyLogFilter1 + Backend databases: + 127.0.0.1:3001 Protocol: MySQLBackend Name: LocalMaster1 + 127.0.0.1:3002 Protocol: MySQLBackend Name: LocalSlave1 + 127.0.0.1:3003 Protocol: MySQLBackend Name: LocalSlave2 + Total connections: 2 + Currently connected: 2 +MaxScale> call command rrrouter test_command "one" 0 +``` + +The result of the `test_command "one" 0` is printed to the terminal MaxScale is +running in: +``` +RoundRobinRouter wishes the Admin a good day. +The module got 2 arguments. +Argument 0: type 'string' value 'one' +Argument 1: type 'boolean' value 'false' +``` + +## Summary and conclusion + +Plugins offer a way to extend MariaDB MaxScale whenever the standard modules are +found insufficient. The plugins need only implement a set API, can be +independently compiled and installation is simply a file copy with some +configuration file modifications. + +Out of the different plugin types, filters are the easiest to implement. They +work independently and have few requirements. Protocol and authenticator modules +require indepth knowledge of the database protocol they implement. Router module +complexity depends on the routing logic requirements. + +The provided RoundRobinRouter example code should serve as a valid starting +point for both filters and routers. Studying the MaxScale Public Interface +headers to get a general idea of what services the core provides for plugins, +is also highly recommeded. + +Lastly, MariaDB MaxScale is an open-source project, so code contributions can be +accepted if they fulfill the +[requirements](https://github.com/mariadb-corporation/MaxScale/wiki/Contributing). diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index acc6e0675..c19a4d52f 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -128,6 +128,7 @@ document. - [Binlog Router Design (in development)](http://mariadb-corporation.github.io/MaxScale/Design-Documents/binlog-router-html-docs) - [DCB States (to be replaced in StarUML)](Design-Documents/DCB-States.pdf) - [Schema Sharding Router Technical Documentation](Design-Documents/SchemaRouter-technical.md) + - [Plugin development guide](Design-Documents/Plugin-development-guide.md) ## Earlier Release Notes @@ -145,4 +146,3 @@ document. - [MariaDB MaxScale 0.7 Release Notes](Release-Notes/MaxScale-0.7-Release-Notes.md) - [MariaDB MaxScale 0.6 Release Notes](Release-Notes/MaxScale-0.6-Release-Notes.md) - [MariaDB MaxScale 0.5 Release Notes](Release-Notes/MaxScale-0.5-Release-Notes.md) - diff --git a/Documentation/Filters/Cache.md b/Documentation/Filters/Cache.md index 02ecd71b8..18798284f 100644 --- a/Documentation/Filters/Cache.md +++ b/Documentation/Filters/Cache.md @@ -3,12 +3,19 @@ This filter was introduced in MariaDB MaxScale 2.1. ## Overview +_Note that the cache is still experimental and that non-backward compatible +changes may be made._ + The cache filter is a simple cache that is capable of caching the result of SELECTs, so that subsequent identical SELECTs are served directly by MaxScale, without the queries being routed to any server. -_Note that the cache is still experimental and that non-backward compatible -changes may be made._ +SELECTs using the following functions will not be cached: `BENCHMARK`, +`CONNECTION_ID`, `CONVERT_TZ`, `CURDATE`, `CURRENT_DATE`, `CURRENT_TIMESTAMP`, +`CURTIME`, `DATABASE`, `ENCRYPT`, `FOUND_ROWS`, `GET_LOCK`, `IS_FREE_LOCK`, +`IS_USED_LOCK`, `LAST_INSERT_ID`, `LOAD_FILE`, `LOCALTIME`, `LOCALTIMESTAMP`, +`MASTER_POS_WAIT`, `NOW`, `RAND`, `RELEASE_LOCK`, `SESSION_USER`, `SLEEP`, +`SYSDATE`, `SYSTEM_USER`, `UNIX_TIMESTAMP`, `USER`, `UUID`, `UUID_SHORT`. Note that installing the cache causes all statements to be parsed. The implication of that is that unless statements _already_ need to be parsed, @@ -26,40 +33,24 @@ Currently there is **no** cache invalidation, apart from _time-to-live_. Resultsets of prepared statements are **not** cached. ### Transactions -The cache will be used and populated **only** if there is _no_ on-going -transaction or if an on-going transaction is _explicitly_ read-only (that is, -`START TRANSACTION READ ONLY`). +The cache will be used and populated in the following circumstances: + +* There is _no_ explicit transaction active, that is, _autocommit_ is used, +* there is an _explicitly_ read-only transaction (that is,`START TRANSACTION + READ ONLY`) active, or +* there is a transaction active and _no_ statement that modify the database + has been performed. + +In practice, the last bullet point basically means that if a transaction has +been started with `BEGIN` or `START TRANSACTION READ WRITE`, then the cache +will be used and populated until the first `UPDATE`, `INSERT` or `DELETE` +statement is encountered. ### Variables -The cache key is effectively the entire _SELECT_ statement. However, the -value of any variables used in the select is **not** considered. For instance, -if a variable is used in the _WHERE_ clause of the select, a subsequent -identical select will return the wrong result, if the value of the variable -has been changed in between. -``` -MySQL [testdb]> create table tbl (a int, b int); -MySQL [testdb]> insert into tbl values (1, 2), (3, 4); - -MySQL [testdb]> set @var=2; -MySQL [testdb]> select a from tbl where b=@var; -+------+ -| a | -+------+ -| 1 | -+------+ - -MySQL [testdb]> set @var=4; -MySQL [testdb]> select a from tbl where b=@var; -+------+ -| a | -+------+ -| 1 | -+------+ -``` -In the second case, the correct answer would have been `3` and not `1`. +If user or system variables are used in the _SELECT_ statement, the result +will not be cached. ### Security - The cache is **not** aware of grants. The implication is that unless the cache has been explicitly configured diff --git a/Documentation/Monitors/MySQL-Monitor.md b/Documentation/Monitors/MySQL-Monitor.md index f0be5738c..45e519ff1 100644 --- a/Documentation/Monitors/MySQL-Monitor.md +++ b/Documentation/Monitors/MySQL-Monitor.md @@ -138,6 +138,7 @@ initiated, the following conditions must have been met: - The monitor has repeatedly failed to connect to the failed servers - There is only one running server among the monitored servers - @@read_only is not enabled on the last running server +- The last running server is not configured as a slave When these conditions are met, the monitor assigns the last remaining server the master status and puts all other servers into maintenance mode. This is done to @@ -167,12 +168,12 @@ milliseconds, the value of _failcount_ must be 10. ### `failover_recovery` Allow recovery after failover. This feature takes a boolean parameter is -disabled by default. +enabled by default. -Normally if a failover has been triggered and the last remaining server is -chosen as the master, the monitor will set all of the failed servers into -maintenance mode. When this option is enabled, the failed servers are allowed to -rejoin the cluster. +When this parameter is disabled, if a failover has been triggered and the last +remaining server is chosen as the master, the monitor will set all of the failed +servers into maintenance mode. When this option is enabled, the failed servers +are allowed to rejoin the cluster. This option should be enabled when failover in MaxScale is used in conjunction with an external agent that resets the slave status for new master servers. One diff --git a/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md index ed375a32c..540f0b5a4 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md @@ -8,6 +8,32 @@ release 2.1.0. For any problems you encounter, please consider submitting a bug report at [Jira](https://jira.mariadb.org). +## Changed Features + +### Cache + +The cache will now _also_ be used and populated in a transaction that is +_not_ explicitly read only, but only until the first statement that modifies +the database is encountered. + +## Dropped Features + +### MaxAdmin + +The following deprecated commands have been removed: + +* `enable log [debug|trace|message]` +* `disable log [debug|trace|message]` +* `enable sessionlog [debug|trace|message]` +* `disable sessionlog [debug|trace|message]` + +The following commands have been deprecated: + +* `enable sessionlog-priority [debug|info|notice|warning]` +* `disable sessionlog-priority [debug|info|notice|warning]` + +The commands can be issued, but have no effect. + ## New Features ### Failover Recovery for MySQL Monitor diff --git a/Documentation/Routers/ReadWriteSplit.md b/Documentation/Routers/ReadWriteSplit.md index 7be11861f..49608eee2 100644 --- a/Documentation/Routers/ReadWriteSplit.md +++ b/Documentation/Routers/ReadWriteSplit.md @@ -100,6 +100,25 @@ The `LEAST_GLOBAL_CONNECTIONS` and `LEAST_ROUTER_CONNECTIONS` use the connection `LEAST_BEHIND_MASTER` does not take server weights into account when choosing a server. +#### Interaction Between `slave_selection_criteria` and `max_slave_connections` + +Depending on the value of `max_slave_connections`, the slave selection criteria +behave in different ways. Here are a few example cases of how the different +criteria work with different amounts of slave connections. + +* With `slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS` and `max_slave_connections=1`, each session picks + one slave and one master + +* With `slave_selection_criteria=LEAST_CURRENT_OPERATIONS` and `max_slave_connections=100%`, each session + picks one master and as many slaves as possible + +* With `slave_selection_criteria=LEAST_CURRENT_OPERATIONS` each read is load balanced based on how many + queries are active on a particular slave + +* With `slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS` each read is sent to the slave with the least + amount of connections + + ### `max_sescmd_history` **`max_sescmd_history`** sets a limit on how many session commands each session can execute before the session command history is disabled. The default is an unlimited number of session commands. diff --git a/cmake/package.cmake b/cmake/package.cmake index 46c93ab11..20f88c52c 100644 --- a/cmake/package.cmake +++ b/cmake/package.cmake @@ -2,11 +2,24 @@ execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE CPACK_PACKAGE_ARCHITECTURE) +# Check target +set(PACK_TARGETS "core" "devel" "external" "all") +if(DEFINED TARGET_COMPONENT AND NOT TARGET_COMPONENT STREQUAL "") + set(LIST_INDEX -1) + list(FIND PACK_TARGETS ${TARGET_COMPONENT} LIST_INDEX) + if (${LIST_INDEX} EQUAL -1) + message(FATAL_ERROR "Unrecognized TARGET_COMPONENT value. Allowed values: ${PACK_TARGETS}.") + endif() +else() + set(TARGET_COMPONENT "core") + message(STATUS "No TARGET_COMPONENT defined, using default value 'core'") +endif() + # Generic CPack configuration variables set(CPACK_SET_DESTDIR ON) set(CPACK_PACKAGE_RELOCATABLE FALSE) set(CPACK_STRIP_FILES FALSE) -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale - The Dynamic Data Routing Platform") set(CPACK_PACKAGE_VERSION_MAJOR "${MAXSCALE_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${MAXSCALE_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${MAXSCALE_VERSION_PATCH}") @@ -15,6 +28,15 @@ set(CPACK_PACKAGE_VENDOR "MariaDB Corporation Ab") set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_SOURCE_DIR}/etc/DESCRIPTION) set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") +# If building devel package, change the description. Deb- and rpm-specific parameters are set in their +# dedicated files "package_(deb/rpm).cmake" +if (TARGET_COMPONENT STREQUAL "devel") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MaxScale plugin development headers") + set(DESCRIPTION_TEXT "\ + This package contains header files required for plugin module development for MariaDB MaxScale. \ +The source of MariaDB MaxScale is not required.") +endif() + # If we're building something other than the main package, append the target name # to the package name. if(DEFINED TARGET_COMPONENT AND NOT TARGET_COMPONENT STREQUAL "core" AND NOT TARGET_COMPONENT STREQUAL "") diff --git a/cmake/package_deb.cmake b/cmake/package_deb.cmake index ee0236753..cbc18f593 100644 --- a/cmake/package_deb.cmake +++ b/cmake/package_deb.cmake @@ -7,6 +7,8 @@ set(CPACK_DEBIAN_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") if(TARGET_COMPONENT STREQUAL "core") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/postinst;${CMAKE_BINARY_DIR}/postrm") +elseif(TARGET_COMPONENT STREQUAL "devel") + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n${DESCRIPTION_TEXT}") endif() if(EXTRA_PACKAGE_DEPENDENCIES) diff --git a/cmake/package_rpm.cmake b/cmake/package_rpm.cmake index d1c4b43e1..d4787c5d2 100644 --- a/cmake/package_rpm.cmake +++ b/cmake/package_rpm.cmake @@ -30,6 +30,8 @@ if(TARGET_COMPONENT STREQUAL "core") # Installing this prevents RPM from deleting the /var/lib/maxscale folder install(DIRECTORY DESTINATION ${MAXSCALE_VARDIR}/lib/maxscale) +elseif(TARGET_COMPONENT STREQUAL "devel") + set(CPACK_RPM_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n${DESCRIPTION_TEXT}") endif() if(EXTRA_PACKAGE_DEPENDENCIES) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..1ec7d614d --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,20 @@ +include_directories(${MARIADB_CONNECTOR_INCLUDE_DIR}) + +add_library(roundrobinrouter SHARED roundrobinrouter.cpp) +target_link_libraries(roundrobinrouter maxscale-common) +set_target_properties(roundrobinrouter PROPERTIES VERSION "1.0.0") +install_module(roundrobinrouter core) + +add_library(testroute SHARED testroute.c) +target_link_libraries(testroute maxscale-common) +set_target_properties(testroute PROPERTIES VERSION "1.0.0") +install_module(testroute core) + +add_library(testfilter SHARED testfilter.c) +target_link_libraries(testfilter maxscale-common) +set_target_properties(testfilter PROPERTIES VERSION "1.0.0") +install_module(testfilter core) + +add_library(testprotocol SHARED testprotocol.c) +set_target_properties(testprotocol PROPERTIES VERSION "1.0.0") +install_module(testprotocol core) diff --git a/examples/roundrobinrouter.cpp b/examples/roundrobinrouter.cpp new file mode 100644 index 000000000..24da84a28 --- /dev/null +++ b/examples/roundrobinrouter.cpp @@ -0,0 +1,897 @@ +/* + * Copyright (c) 2017 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +/** + * @file roundrobinrouter.c - Round-robin router load balancer + * + * This is an implementation of a simple query router that balances reads on a + * query level. The router is configured with a set of slaves and optionally + * a master. The router balances the client read queries over the set of slave + * servers, sending write operations to the master. Session-operations are sent + * to all slaves and the master. The read query balancing is done in round robin + * style: in each session, the slave servers (and the master if inserted into the + * slave list) take turns processing read queries. + * + * This router is intended to be a rather straightforward example on how to + * program a module for MariaDB MaxScale. The router does not yet support all + * SQL-commands and there are bound to be various limitations yet unknown. It + * does work on basic reads and writes. + * + */ + +/* The log macros use this definition. */ +#define MXS_MODULE_NAME "RoundRobinRouter" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG_RRROUTER +#undef DEBUG_RROUTER + +#ifdef DEBUG_RRROUTER +#define RR_DEBUG(msg, ...) MXS_NOTICE(msg, ##__VA_ARGS__) +#else +#define RR_DEBUG(msg, ...) +#endif + +/* This router handles different query types in a different manner. Some queries + * require that a "write_backend" is set. */ +const uint32_t q_route_to_rr = (QUERY_TYPE_LOCAL_READ | QUERY_TYPE_READ | + QUERY_TYPE_MASTER_READ | QUERY_TYPE_USERVAR_READ | + QUERY_TYPE_SYSVAR_READ | QUERY_TYPE_GSYSVAR_READ | + QUERY_TYPE_SHOW_DATABASES | QUERY_TYPE_SHOW_TABLES); + +const uint32_t q_route_to_all = (QUERY_TYPE_SESSION_WRITE | QUERY_TYPE_USERVAR_WRITE | + QUERY_TYPE_GSYSVAR_WRITE | QUERY_TYPE_ENABLE_AUTOCOMMIT | + QUERY_TYPE_DISABLE_AUTOCOMMIT); + +const uint32_t q_trx_begin = QUERY_TYPE_BEGIN_TRX; + +const uint32_t q_trx_end = (QUERY_TYPE_ROLLBACK | QUERY_TYPE_COMMIT); + +const uint32_t q_route_to_write = (QUERY_TYPE_WRITE | QUERY_TYPE_PREPARE_NAMED_STMT | + QUERY_TYPE_PREPARE_STMT | QUERY_TYPE_EXEC_STMT | + QUERY_TYPE_CREATE_TMP_TABLE | QUERY_TYPE_READ_TMP_TABLE); + +const char MAX_BACKENDS[] = "max_backends"; +const char WRITE_BACKEND[] = "write_backend"; +const char PRINT_ON_ROUTING[] = "print_on_routing"; +const char DUMMY[] = "dummy_setting"; + +/* Enum setting definition example */ +static const MXS_ENUM_VALUE enum_example[] = +{ + {"two", 2}, + {"zero", 0}, + {NULL} /* Last must be NULL */ +}; + +static modulecmd_arg_type_t custom_cmd_args[] = +{ + {MODULECMD_ARG_STRING, "Example string"}, + {(MODULECMD_ARG_BOOLEAN | MODULECMD_ARG_OPTIONAL), "This is an optional bool parameter"} +}; + +bool custom_cmd_example(const MODULECMD_ARG *argv); + +using std::string; +using std::cout; + +typedef std::vector DCB_VEC; + +class RRRouter; +class RRRouterSession; + +/* Each service using this router will have a router object instance. */ +class RRRouter : public MXS_ROUTER +{ +private: + SERVICE* m_service; /* Service this router is part of */ + /* Router settings */ + unsigned int m_max_backends; /* How many backend servers to use */ + SERVER* m_write_server; /* Where to send write etc. "unsafe" queries */ + bool m_print_on_routing; /* Print a message on every packet routed? */ + uint64_t m_example_enum; /* Not used */ + + void decide_target(RRRouterSession* rses, GWBUF* querybuf, DCB*& target, bool& route_to_all); +public: + /* Statistics, written to by multiple threads */ + volatile unsigned long int m_routing_s; /* Routing success */ + volatile unsigned long int m_routing_f; /* Routing fail */ + volatile unsigned long int m_routing_c; /* Client packets routed */ + + /* Methods */ + RRRouter(SERVICE* service); + ~RRRouter(); + RRRouterSession* create_session(MXS_SESSION* session); + int route_query(RRRouterSession* rses, GWBUF* querybuf); + void client_reply(RRRouterSession* rses, GWBUF* buf, DCB* backend_dcb); + void handle_error(RRRouterSession* rses, GWBUF* message, DCB* problem_dcb, + mxs_error_action_t action, bool* succp); +}; + +/* Every client connection has a corresponding session. */ +class RRRouterSession : public MXS_ROUTER_SESSION +{ +public: + bool m_closed; /* true when closeSession is called */ + DCB_VEC m_backend_dcbs; /* backends */ + DCB* m_write_dcb; /* write backend */ + DCB* m_client_dcb; /* client */ + unsigned int m_route_count; /* how many packets have been routed */ + bool m_on_transaction; /* Is the session in transaction mode? */ + unsigned int m_replies_to_ignore; /* Counts how many replies should be ignored. */ + + RRRouterSession(DCB_VEC&, DCB*, DCB*); + ~RRRouterSession(); + void close(); +}; + +RRRouter::RRRouter(SERVICE* service) + : m_service(service) + , m_routing_s(0) + , m_routing_f(0) + , m_routing_c(0) +{ + RR_DEBUG("Creating instance."); + /* Read options specific to round robin router. */ + MXS_CONFIG_PARAMETER* params = service->svc_config_param; + m_max_backends = config_get_integer(params, MAX_BACKENDS); + m_write_server = config_get_server(params, WRITE_BACKEND); + m_print_on_routing = config_get_bool(params, PRINT_ON_ROUTING); + m_example_enum = config_get_enum(params, DUMMY, enum_example); + + RR_DEBUG("Settings read:"); + RR_DEBUG("'%s': %d", MAX_BACKENDS, m_max_backends); + RR_DEBUG("'%s': %p", WRITE_BACKEND, m_write_server); + RR_DEBUG("'%s': %d", PRINT_ON_ROUTING, m_print_on_routing); + RR_DEBUG("'%s': %lu", DUMMY, m_example_enum); +} +RRRouter::~RRRouter() +{ + RR_DEBUG("Deleting router instance."); + RR_DEBUG("Queries routed successfully: %lu", m_routing_s); + RR_DEBUG("Failed routing attempts: %lu", m_routing_f); + RR_DEBUG("Client replies: %lu", m_routing_c); +} + +RRRouterSession* RRRouter::create_session(MXS_SESSION* session) +{ + DCB_VEC backends; + DCB* write_dcb = NULL; + RRRouterSession* rses = NULL; + try + { + /* Try to connect to as many backends as required. */ + SERVER_REF* sref; + for (sref = m_service->dbref; sref != NULL; sref = sref->next) + { + if (SERVER_REF_IS_ACTIVE(sref) && (backends.size() < m_max_backends)) + { + /* Connect to server */ + DCB* conn = dcb_connect(sref->server, session, + sref->server->protocol); + if (conn) + { + /* Success */ + atomic_add(&sref->connections, 1); + conn->service = session->service; + backends.push_back(conn); + } /* Any error by dcb_connect is reported by the function itself */ + } + } + if (m_write_server) + { + /* Connect to write backend server. This is not essential. */ + write_dcb = dcb_connect(m_write_server, session, m_write_server->protocol); + if (write_dcb) + { + /* Success */ + write_dcb->service = session->service; + } + } + if (backends.size() < 1) + { + MXS_ERROR("Session creation failed, could not connect to any " + "read backends."); + } + else + { + rses = new RRRouterSession(backends, write_dcb, session->client_dcb); + RR_DEBUG("Session with %lu connections created.", + backends.size() + (write_dcb ? 1 : 0)); + } + } + catch (const std::exception& x) + { + MXS_ERROR("Caught exception: %s", x.what()); + /* Close any connections already made */ + for (unsigned int i = 0; i < backends.size(); i++) + { + DCB* dcb = backends[i]; + dcb_close(dcb); + atomic_add(&(m_service->dbref->connections), -1); + } + backends.clear(); + if (write_dcb) + { + dcb_close(write_dcb); + } + } + return rses; +} + +int RRRouter::route_query(RRRouterSession* rses, GWBUF* querybuf) +{ + int rval = 0; + const bool print = m_print_on_routing; + DCB* target = NULL; + bool route_to_all = false; + + if (!rses->m_closed) + { + decide_target(rses, querybuf, target, route_to_all); + } + + /* Target selection done, write to dcb. */ + if (target) + { + /* We have one target backend */ + if (print) + { + MXS_NOTICE("Routing statement of length %du to backend '%s'.", + gwbuf_length(querybuf), target->server->unique_name); + } + /* Do not use dcb_write() to output to a dcb. dcb_write() is used only + * for raw write in the procol modules. */ + rval = target->func.write(target, querybuf); + /* After write, the buffer points to non-existing data. */ + querybuf = NULL; + } + else if (route_to_all) + { + int n_targets = rses->m_backend_dcbs.size() + (rses->m_write_dcb ? 1 : 0); + if (print) + { + MXS_NOTICE("Routing statement of length %du to %d backends.", + gwbuf_length(querybuf), n_targets); + } + int route_success = 0; + for (unsigned int i = 0; i < rses->m_backend_dcbs.size(); i++) + { + DCB* dcb = rses->m_backend_dcbs[i]; + /* Need to clone the buffer since write consumes it */ + GWBUF* copy = gwbuf_clone(querybuf); + if (copy) + { + route_success += dcb->func.write(dcb, copy); + } + } + if (rses->m_write_dcb) + { + GWBUF* copy = gwbuf_clone(querybuf); + if (copy) + { + route_success += rses->m_write_dcb->func.write(rses->m_write_dcb, + copy); + } + } + rses->m_replies_to_ignore += route_success - 1; + rval = (route_success == n_targets) ? 1 : 0; + gwbuf_free(querybuf); + } + else + { + MXS_ERROR("Could not find a valid routing backend. Either the " + "'%s' is not set or the command is not recognized.", + WRITE_BACKEND); + gwbuf_free(querybuf); + } + if (rval == 1) + { + /* Non-atomic update of shared data, but contents are non-essential */ + m_routing_s++; + } + else + { + m_routing_f++; + } + return rval; +} + +void RRRouter::client_reply(RRRouterSession* rses, GWBUF* buf, DCB* backend_dcb) +{ + if (rses->m_replies_to_ignore > 0) + { + /* In this case MaxScale cloned the message to many backends but the client + * expects just one reply. Assume that client does not send next query until + * previous has been answered. + */ + rses->m_replies_to_ignore--; + gwbuf_free(buf); + return; + } + + MXS_SESSION_ROUTE_REPLY(backend_dcb->session, buf); + + m_routing_c++; + if (m_print_on_routing) + { + MXS_NOTICE("Replied to client.\n"); + } +} +void RRRouter::handle_error(RRRouterSession* rses, GWBUF* message, DCB* problem_dcb, + mxs_error_action_t action, bool* succp) +{ + /* Don't handle same error twice on same DCB */ + if (problem_dcb->dcb_errhandle_called) + { + /* Assume that previous call succeed. */ + *succp = true; + return; + } + else + { + problem_dcb->dcb_errhandle_called = true; + } + + MXS_SESSION* session = problem_dcb->session; + DCB* client_dcb = session->client_dcb; + mxs_session_state_t sesstate = session->state; + + /* If the erroneous dcb is a client handler, close it. Setting succp to + * false will cause the entire attached session to be closed. + */ + if (problem_dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER) + { + dcb_close(problem_dcb); + *succp = false; + } + else + { + switch (action) + { + case ERRACT_REPLY_CLIENT: + { + /* React to failed authentication, send message to client */ + if (sesstate == SESSION_STATE_ROUTER_READY) + { + /* Send error report to client */ + GWBUF* copy = gwbuf_clone(message); + if (copy) + { + client_dcb->func.write(client_dcb, copy); + } + } + *succp = false; + } + break; + case ERRACT_NEW_CONNECTION: + { + /* React to a failed backend */ + if (problem_dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER) + { + if (problem_dcb == rses->m_write_dcb) + { + dcb_close(rses->m_write_dcb); + rses->m_write_dcb = NULL; + } + else + { + /* Find dcb in the list of backends */ + DCB_VEC::iterator iter = rses->m_backend_dcbs.begin(); + while (iter != rses->m_backend_dcbs.end()) + { + if (*iter == problem_dcb) + { + dcb_close(*iter); + rses->m_backend_dcbs.erase(iter); + break; + } + } + } + + /* If there is still backends remaining, return true since + * router can still function. + */ + *succp = (rses->m_backend_dcbs.size() > 0) ? true : false; + } + } + break; + default: + ss_dassert(!true); + *succp = false; + break; + } + } +} + +RRRouterSession::RRRouterSession(DCB_VEC& backends, DCB* write, DCB* client) + : m_closed(false) + , m_route_count(0) + , m_on_transaction(false) + , m_replies_to_ignore(0) +{ + m_backend_dcbs = backends; + m_write_dcb = write; + m_client_dcb = client; +} + +RRRouterSession::~RRRouterSession() +{ + /* Shouldn't happen. */ + ss_dassert(m_closed); +} + +void RRRouterSession::close() +{ + if (!m_closed) + { + /** + * Mark router session as closed. @c m_closed is checked at the start + * of most API functions to quickly stop the processing of closed sessions. + */ + m_closed = true; + int closed_conns = 0; + for (unsigned int i = 0; i < m_backend_dcbs.size(); i++) + { + DCB* dcb = m_backend_dcbs[i]; + SERVER_REF* sref = dcb->service->dbref; + dcb_close(dcb); + closed_conns++; + atomic_add(&(sref->connections), -1); + } + m_backend_dcbs.clear(); + if (m_write_dcb) + { + dcb_close(m_write_dcb); + m_write_dcb = NULL; + closed_conns++; + } + RR_DEBUG("Session with %d connections closed.", closed_conns); + } +} + +void RRRouter::decide_target(RRRouterSession* rses, GWBUF* querybuf, DCB*& target, bool& route_to_all) +{ + /* Extract the command type from the SQL-buffer */ + mysql_server_cmd_t cmd_type = MYSQL_GET_COMMAND(GWBUF_DATA(querybuf)); + /* The "query_types" is only really valid for query-commands but let's use + * it here for all command types. + */ + uint32_t query_types = 0; + + switch (cmd_type) + { + case MYSQL_COM_QUERY: + { + /* Use the inbuilt query_classifier to get information about + * the query. The default qc works with mySQL-queries. + */ + query_types = qc_get_type_mask(querybuf); + +#ifdef DEBUG_RRROUTER + char* zSql_query = NULL; + int length = 0; + modutil_extract_SQL(querybuf, &zSql_query, &length); + string sql_query(zSql_query, length); + RR_DEBUG("QUERY: %s", sql_query.c_str()); +#endif + } + break; + case MYSQL_COM_INIT_DB: + query_types = q_route_to_all; + RR_DEBUG("MYSQL_COM_INIT_DB"); + break; + case MYSQL_COM_QUIT: + query_types = q_route_to_all; + RR_DEBUG("MYSQL_COM_QUIT"); + break; + case MYSQL_COM_FIELD_LIST: + query_types = q_route_to_rr; + RR_DEBUG("MYSQL_COM_FIELD_LIST"); + break; + default: + /* TODO: Add support for other commands if needed. */ + /* This error message will only print the number of the cmd. */ + MXS_ERROR("Received unexpected sql command type: '%d'.", cmd_type); + break; + } + + if ((query_types & q_route_to_write) != 0) + { + target = rses->m_write_dcb; + } + else + { + /* This is not yet sufficient for handling transactions. */ + if ((query_types & q_trx_begin) != 0) + { + rses->m_on_transaction = true; + } + if (rses->m_on_transaction) + { + /* If a transaction is going on, route all to write backend */ + target = rses->m_write_dcb; + } + if ((query_types & q_trx_end) != 0) + { + rses->m_on_transaction = false; + } + + if (!target && ((query_types & q_route_to_rr) != 0)) + { + /* Round robin backend. */ + unsigned int index = (rses->m_route_count++) % rses->m_backend_dcbs.size(); + target = rses->m_backend_dcbs[index]; + } + /* Some commands and queries are routed to all backends. */ + else if (!target && ((query_types & q_route_to_all) != 0)) + { + route_to_all = true; + } + } +} + +/* + * The functions implementing the router module API. These do not need to be + * "extern C", but they do need to be callable from C code. + */ +static MXS_ROUTER* createInstance(SERVICE* service, char** options); +static MXS_ROUTER_SESSION* newSession(MXS_ROUTER* instance, MXS_SESSION* session); +static void closeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session); +static void freeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session); +static int routeQuery(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* querybuf); +static void diagnostics(MXS_ROUTER* instance, DCB* dcb); +static void clientReply(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session, + GWBUF* resultbuf, DCB* backend_dcb); +static void handleError(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session, + GWBUF* errmsgbuf, DCB* backend_dcb, mxs_error_action_t action, + bool* succp); +static uint64_t getCapabilities(MXS_ROUTER *instance); +static void destroyInstance(MXS_ROUTER* instance); +/* The next two entry points are usually optional. */ +static int process_init(); +static void process_finish(); + +/* + * This is called by the module loader during MaxScale startup. A module + * description, including entrypoints and allowed configuration parameters, + * is returned. This function must be exported. + */ +extern "C" MXS_MODULE* MXS_CREATE_MODULE(); +MXS_MODULE* MXS_CREATE_MODULE() +{ + static MXS_ROUTER_OBJECT entryPoints = + { + createInstance, + newSession, + closeSession, + freeSession, + routeQuery, + diagnostics, + clientReply, + handleError, + getCapabilities, + destroyInstance + }; + + static MXS_MODULE moduleObject = + { + MXS_MODULE_API_ROUTER, /* Module type */ + MXS_MODULE_BETA_RELEASE, /* Release status */ + MXS_ROUTER_VERSION, /* Implemented module API version */ + "A simple round robin router", /* Description */ + "V1.1.0", /* Module version */ + &entryPoints, /* Defined above */ + process_init, /* Process init, can be null */ + process_finish, /* Process finish, can be null */ + NULL, /* Thread init */ + NULL, /* Thread finish */ + { + /* Next is an array of MODULE_PARAM structs, max 64 items. These define all + * the possible parameters that this module accepts. This is required + * since the module loader also parses the configuration file for the module. + * Any unrecognised parameters in the config file are discarded. + * + * Note that many common parameters, such as backend servers, are + * already set to the upper level "service"-object. + */ + { /* For simple types, only 3 of the 5 struct fields need to be + * defined. */ + MAX_BACKENDS, /* Setting identifier in maxscale.cnf */ + MXS_MODULE_PARAM_INT, /* Setting type */ + "0" /* Default value */ + }, + {PRINT_ON_ROUTING, MXS_MODULE_PARAM_BOOL, "false"}, + {WRITE_BACKEND, MXS_MODULE_PARAM_SERVER, NULL}, + { /* Enum types require an array with allowed values. */ + DUMMY, + MXS_MODULE_PARAM_ENUM, + "the_answer", + MXS_MODULE_OPT_NONE, + enum_example + }, + {MXS_END_MODULE_PARAMS} + } + }; + return &moduleObject; +} + +/** + * @brief Create an instance of the router (API). + * + * Create an instance of the round robin router. One instance of the router is + * created for each service that is defined in the configuration as using this + * router. One instance of the router will handle multiple connections + * (router sessions). + * + * @param service The service this router is being created for + * @param options The options for this query router + * @return NULL in failure, pointer to router in success. + */ +static MXS_ROUTER* createInstance(SERVICE* service, char** options) +{ + RRRouter* instance = NULL; + /* The core of MaxScale is written in C and does not understand exceptions. + * The macro catches all exceptions. Add custom handling here if required. */ + MXS_EXCEPTION_GUARD(instance = new RRRouter(service)); + /* The MXS_ROUTER is just a void*. Only the router module will dereference + * the pointer. */ + + /* Register a custom command */ + if (!modulecmd_register_command("rrrouter", "test_command", custom_cmd_example, + 2, custom_cmd_args)) + { + MXS_ERROR("Module command registration failed."); + } + return instance; +} + +/** + * @brief Create a new router session for this router instance (API). + * + * Connect a client session to the router instance and return a router session. + * The router session stores all client specific data required by the router. + * + * @param instance The router object instance + * @param session The MaxScale session (generic client connection data) + * @return Client specific data for this router + */ +static MXS_ROUTER_SESSION* newSession(MXS_ROUTER* instance, MXS_SESSION* session) +{ + RRRouter* router = static_cast(instance); + RRRouterSession* rses = NULL; + MXS_EXCEPTION_GUARD(rses = router->create_session(session)); + return rses; +} +/** + * @brief Close an existing router session for this router instance (API). + * + * Close a client session attached to the router instance. This function should + * close connections and release other resources allocated in "newSession" or + * otherwise held by the router session. This function should NOT free the + * session object itself. + * + * @param instance The router object instance + * @param session The router session + */ +static void closeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session) +{ + RRRouterSession* rses = static_cast(session); + MXS_EXCEPTION_GUARD(rses->close()); +} + +/** + * @brief Free a router session (API). + * + * When a router session has been closed, freeSession may be called to free + * allocated resources. + * + * @param instance The router instance the session belongs to + * @param session Router client session + * + */ +static void freeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session) +{ + RRRouterSession* rses = static_cast(session); + delete rses; +} + +/** + * @brief Route a packet (API) + * + * The routeQuery function receives a packet and makes the routing decision + * based on the contents of the router instance, router session and the query + * itself. It then sends the query to the target backend(s). + * + * @param instance Router instance + * @param session Router session associated with the client + * @param buffer Buffer containing the query (or command) + * @return 1 on success, 0 on error + */ +static int routeQuery(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* buffer) +{ + RRRouter* router = static_cast(instance); + RRRouterSession* rses = static_cast(session); + int rval = 0; + MXS_EXCEPTION_GUARD(rval = router->route_query(rses, buffer)); + return rval; +} + +/** + * @brief Diagnostics routine (API) + * + * Print router statistics to the DCB passed in. This is usually called by the + * MaxInfo or MaxAdmin modules. + * + * @param instance The router instance + * @param dcb The DCB for diagnostic output + */ +static void diagnostics(MXS_ROUTER* instance, DCB* dcb) +{ + RRRouter* router = static_cast(instance); + dcb_printf(dcb, "\t\tQueries routed successfully: %lu\n", router->m_routing_s); + dcb_printf(dcb, "\t\tFailed routing attempts: %lu\n", router->m_routing_f); + dcb_printf(dcb, "\t\tClient replies routed: %lu\n", router->m_routing_c); +} + +/** + * @brief Client Reply routine (API) + * + * This routine receives a packet from a backend server meant for the client. + * Often, there is little logic needed and the packet can just be forwarded to + * the next element in the processing chain. + * + * @param instance The router instance + * @param session The router session + * @param backend_dcb The backend DCB (data source) + * @param queue The GWBUF with reply data + */ +static void clientReply(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* queue, + DCB* backend_dcb) +{ + RRRouter* router = static_cast(instance); + RRRouterSession* rses = static_cast(session); + MXS_EXCEPTION_GUARD(router->client_reply(rses, queue, backend_dcb)); +} + +/** + * Error Handler routine (API) + * + * This routine will handle errors that occurred with the session. This function + * is called if routeQuery() returns 0 instead of 1. The client or a backend + * unexpectedly closing a connection also triggers this routine. + * + * @param instance The router instance + * @param session The router session + * @param message The error message to reply + * @param problem_dcb The DCB related to the error + * @param action The action: ERRACT_NEW_CONNECTION or ERRACT_REPLY_CLIENT + * @param succp Output result of action, true if router can continue + */ +static void handleError(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, + GWBUF* message, DCB* problem_dcb, + mxs_error_action_t action, bool* succp) +{ + RRRouter* router = static_cast(instance); + RRRouterSession* rses = static_cast(session); + MXS_EXCEPTION_GUARD(router->handle_error(rses, message, problem_dcb, action, succp)); +} + +/** + * @brief Get router capabilities (API) + * + * Return a bit map indicating the characteristics of this router type. + * In this case, the only bit set indicates that the router wants to receive + * data for routing as whole SQL statements. + * + * @param instance The router instance + * @return RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_STMT_OUTPUT + */ +static uint64_t getCapabilities(MXS_ROUTER *instance) +{ + /* This router needs to parse client queries, so it should set RCAP_TYPE_CONTIGUOUS_INPUT. + * For output, parsing is not required but counting SQL replies is. RCAP_TYPE_RESULTSET_OUTPUT + * should be sufficient. + */ + return RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_RESULTSET_OUTPUT; +} + +/** + * @brief Destroy router instance (API) + * + * A destroy-function is rarely required, since this is usually called only at + * program exit. + * + * @param instance Router instance + */ +static void destroyInstance(MXS_ROUTER* instance) +{ + RRRouter* router = static_cast(instance); + delete router; +} + +/** + * Make any initializations required by the router module as a whole and not + * specific to any individual router instance. + * + * @return 0 on success + */ +static int process_init() +{ + RR_DEBUG("Module loaded."); + return 0; +} +/** + * Undo module initializations. + */ +static void process_finish() +{ + RR_DEBUG("Module unloaded."); +} + +/** + * A function executed as a custom module command through MaxAdmin + * @param argv The arguments + */ +bool custom_cmd_example(const MODULECMD_ARG *argv) +{ + cout << MXS_MODULE_NAME << " wishes the Admin a good day.\n"; + int n_args = argv->argc; + cout << "The module got " << n_args << " arguments.\n"; + for (int i = 0; i < n_args; i++) + { + arg_node node = argv->argv[i]; + string type_str; + string val_str; + switch (MODULECMD_GET_TYPE(&node.type)) + { + case MODULECMD_ARG_STRING: + { + type_str = "string"; + val_str.assign(node.value.string); + } + break; + case MODULECMD_ARG_BOOLEAN: + { + type_str = "boolean"; + val_str.assign((node.value.boolean) ? "true" : "false"); + } + break; + default: + { + type_str = "other"; + val_str.assign("unknown"); + } + break; + } + cout << "Argument " << i << ": type '" << type_str << "' value '" << val_str << + "'\n"; + } + return true; +} diff --git a/server/modules/filter/testfilter/testfilter.c b/examples/testfilter.c similarity index 100% rename from server/modules/filter/testfilter/testfilter.c rename to examples/testfilter.c diff --git a/server/modules/protocol/testprotocol/testprotocol.c b/examples/testprotocol.c similarity index 100% rename from server/modules/protocol/testprotocol/testprotocol.c rename to examples/testprotocol.c diff --git a/server/modules/routing/testroute/testroute.c b/examples/testroute.c similarity index 63% rename from server/modules/routing/testroute/testroute.c rename to examples/testroute.c index 8f593a811..3f2aeccf1 100644 --- a/server/modules/routing/testroute/testroute.c +++ b/examples/testroute.c @@ -15,20 +15,20 @@ #include #include -static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *session); -static void freeSession(MXS_ROUTER *instance, void *session); -static int routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue); -static void clientReply(MXS_ROUTER *instance, void *session, GWBUF *queue, DCB*); -static void diagnostic(MXS_ROUTER *instance, DCB *dcb); -static uint64_t getCapabilities(MXS_ROUTER* instance); -static void handleError(MXS_ROUTER *instance, - void *router_session, - GWBUF *errbuf, - DCB *backend_dcb, - mxs_error_action_t action, - bool *succp); +static MXS_ROUTER *createInstance(SERVICE *service, char **options); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue); +static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue, DCB*); +static void diagnostic(MXS_ROUTER *instance, DCB *dcb); +static uint64_t getCapabilities(MXS_ROUTER* instance); +static void handleError(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *errbuf, + DCB *backend_dcb, + mxs_error_action_t action, + bool *succp); typedef struct { @@ -104,10 +104,10 @@ createInstance(SERVICE *service, char **options) * @param session The session itself * @return Session specific data for this session */ -static void * +static MXS_ROUTER_SESSION * newSession(MXS_ROUTER *instance, MXS_SESSION *session) { - return (MXS_SESSION*)MXS_MALLOC(sizeof(TESTSESSION)); + return (MXS_ROUTER_SESSION*)MXS_MALLOC(sizeof(TESTSESSION)); } /** @@ -118,24 +118,23 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session) * @param session The session being closed */ static void -closeSession(MXS_ROUTER *instance, void *session) +closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session) { } -static void freeSession( - MXS_ROUTER* router_instance, - void* router_client_session) +static void freeSession(MXS_ROUTER* router_instance, + MXS_ROUTER_SESSION* router_client_session) { MXS_FREE(router_client_session); } static int -routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue) +routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue) { return 0; } -void clientReply(MXS_ROUTER* instance, void* session, GWBUF* queue, DCB* dcb) +void clientReply(MXS_ROUTER* instance, MXS_ROUTER_SESSION* session, GWBUF* queue, DCB* dcb) { } @@ -156,12 +155,11 @@ static uint64_t getCapabilities(MXS_ROUTER* instance) } -static void handleError( - MXS_ROUTER *instance, - void *router_session, - GWBUF *errbuf, - DCB *backend_dcb, - mxs_error_action_t action, - bool *succp) +static void handleError(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *errbuf, + DCB *backend_dcb, + mxs_error_action_t action, + bool *succp) { } diff --git a/include/maxscale/CMakeLists.txt b/include/maxscale/CMakeLists.txt index 3b93a804e..d00a3ea8d 100644 --- a/include/maxscale/CMakeLists.txt +++ b/include/maxscale/CMakeLists.txt @@ -23,3 +23,5 @@ file(GLOB GENERATD_HEADERS "${CMAKE_BINARY_DIR}/include/maxscale/*.hh") foreach(var ${GENERATD_HEADERS}) install_header(${var} devel) endforeach() + +add_subdirectory(protocol) diff --git a/include/maxscale/filter.h b/include/maxscale/filter.h index d7874d512..0d2bc7918 100644 --- a/include/maxscale/filter.h +++ b/include/maxscale/filter.h @@ -33,7 +33,9 @@ MXS_BEGIN_DECLS * from the @c createInstance function of a filter module and subsequently * passing it back to the API functions of the filter. */ -typedef void *MXS_FILTER; +typedef struct mxs_filter +{ +} MXS_FILTER; /** * MXS_FILTER_SESSION is an opaque type representing the session related @@ -43,7 +45,9 @@ typedef void *MXS_FILTER; * from the @c newSession function of a filter module and subsequently * passing it back to the API functions of the filter. */ -typedef void *MXS_FILTER_SESSION; +typedef struct mxs_filter_session +{ +} MXS_FILTER_SESSION; /** * @verbatim diff --git a/include/maxscale/filter.hh b/include/maxscale/filter.hh index 67ef4f6c8..1cb9b90c2 100644 --- a/include/maxscale/filter.hh +++ b/include/maxscale/filter.hh @@ -28,7 +28,7 @@ namespace maxscale * are virtual. That is by design, as the class will be used in a context where * the concrete class is known. That is, there is no need for the virtual mechanism. */ -class FilterSession +class FilterSession : public MXS_FILTER_SESSION { public: /** @@ -200,7 +200,7 @@ protected: * @endcode */ template -class Filter +class Filter : public MXS_FILTER { public: static MXS_FILTER* createInstance(const char* zName, char** pzOptions, MXS_CONFIG_PARAMETER* ppParams) @@ -209,36 +209,36 @@ public: MXS_EXCEPTION_GUARD(pFilter = FilterType::create(zName, pzOptions, ppParams)); - return reinterpret_cast(pFilter); + return pFilter; } static MXS_FILTER_SESSION* newSession(MXS_FILTER* pInstance, MXS_SESSION* pSession) { - FilterType* pFilter = reinterpret_cast(pInstance); - void* pFilterSession; + FilterType* pFilter = static_cast(pInstance); + FilterSessionType* pFilterSession; MXS_EXCEPTION_GUARD(pFilterSession = pFilter->newSession(pSession)); - return reinterpret_cast(pFilterSession); + return pFilterSession; } static void closeSession(MXS_FILTER*, MXS_FILTER_SESSION* pData) { - FilterSessionType* pFilterSession = reinterpret_cast(pData); + FilterSessionType* pFilterSession = static_cast(pData); MXS_EXCEPTION_GUARD(pFilterSession->close()); } static void freeSession(MXS_FILTER*, MXS_FILTER_SESSION* pData) { - FilterSessionType* pFilterSession = reinterpret_cast(pData); + FilterSessionType* pFilterSession = static_cast(pData); MXS_EXCEPTION_GUARD(delete pFilterSession); } static void setDownstream(MXS_FILTER*, MXS_FILTER_SESSION* pData, MXS_DOWNSTREAM* pDownstream) { - FilterSessionType* pFilterSession = reinterpret_cast(pData); + FilterSessionType* pFilterSession = static_cast(pData); typename FilterSessionType::Downstream down(*pDownstream); @@ -247,7 +247,7 @@ public: static void setUpstream(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, MXS_UPSTREAM* pUpstream) { - FilterSessionType* pFilterSession = reinterpret_cast(pData); + FilterSessionType* pFilterSession = static_cast(pData); typename FilterSessionType::Upstream up(*pUpstream); @@ -256,7 +256,7 @@ public: static int routeQuery(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, GWBUF* pPacket) { - FilterSessionType* pFilterSession = reinterpret_cast(pData); + FilterSessionType* pFilterSession = static_cast(pData); int rv = 0; MXS_EXCEPTION_GUARD(rv = pFilterSession->routeQuery(pPacket)); @@ -266,7 +266,7 @@ public: static int clientReply(MXS_FILTER* pInstance, MXS_FILTER_SESSION* pData, GWBUF* pPacket) { - FilterSessionType* pFilterSession = reinterpret_cast(pData); + FilterSessionType* pFilterSession = static_cast(pData); int rv = 0; MXS_EXCEPTION_GUARD(rv = pFilterSession->clientReply(pPacket)); @@ -278,13 +278,13 @@ public: { if (pData) { - FilterSessionType* pFilterSession = reinterpret_cast(pData); + FilterSessionType* pFilterSession = static_cast(pData); MXS_EXCEPTION_GUARD(pFilterSession->diagnostics(pDcb)); } else { - FilterType* pFilter = reinterpret_cast(pInstance); + FilterType* pFilter = static_cast(pInstance); MXS_EXCEPTION_GUARD(pFilter->diagnostics(pDcb)); } @@ -294,7 +294,7 @@ public: { uint64_t rv = 0; - FilterType* pFilter = reinterpret_cast(pInstance); + FilterType* pFilter = static_cast(pInstance); MXS_EXCEPTION_GUARD(rv = pFilter->getCapabilities()); @@ -303,7 +303,7 @@ public: static void destroyInstance(MXS_FILTER* pInstance) { - FilterType* pFilter = reinterpret_cast(pInstance); + FilterType* pFilter = static_cast(pInstance); MXS_EXCEPTION_GUARD(delete pFilter); } diff --git a/include/maxscale/protocol/CMakeLists.txt b/include/maxscale/protocol/CMakeLists.txt index cf37d2404..e12c05ed9 100644 --- a/include/maxscale/protocol/CMakeLists.txt +++ b/include/maxscale/protocol/CMakeLists.txt @@ -1,5 +1,5 @@ file(GLOB HEADERS "*.h*") foreach(var ${HEADERS}) get_filename_component(header ${var} NAME) - install_header(${header} devel) + install_custom_file(${header} ${CMAKE_INSTALL_INCLUDEDIR}/maxscale/protocol devel) endforeach() diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 2847d7b5a..14f959add 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -84,13 +84,12 @@ MXS_BEGIN_DECLS #define MYSQL_CHARSET_OFFSET 12 #define MYSQL_CLIENT_CAP_OFFSET 4 #define MYSQL_CLIENT_CAP_SIZE 4 +#define MARIADB_CAP_OFFSET MYSQL_CHARSET_OFFSET + 19 #define GW_MYSQL_PROTOCOL_VERSION 10 // version is 10 #define GW_MYSQL_HANDSHAKE_FILLER 0x00 -#define GW_MYSQL_SERVER_CAPABILITIES_BYTE1 0xff -#define GW_MYSQL_SERVER_CAPABILITIES_BYTE2 0xf7 #define GW_MYSQL_SERVER_LANGUAGE 0x08 -#define GW_MYSQL_MAX_PACKET_LEN 0xffffffL; +#define GW_MYSQL_MAX_PACKET_LEN 0xffffffL #define GW_MYSQL_SCRAMBLE_SIZE 20 #define GW_SCRAMBLE_LENGTH_323 8 @@ -190,7 +189,8 @@ typedef struct mysql_session typedef enum { GW_MYSQL_CAPABILITIES_NONE = 0, - GW_MYSQL_CAPABILITIES_LONG_PASSWORD = (1 << 0), + /** This is sent by pre-10.2 clients */ + GW_MYSQL_CAPABILITIES_CLIENT_MYSQL = (1 << 0), GW_MYSQL_CAPABILITIES_FOUND_ROWS = (1 << 1), GW_MYSQL_CAPABILITIES_LONG_FLAG = (1 << 2), GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB = (1 << 3), @@ -210,35 +210,60 @@ typedef enum GW_MYSQL_CAPABILITIES_MULTI_RESULTS = (1 << 17), GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS = (1 << 18), GW_MYSQL_CAPABILITIES_PLUGIN_AUTH = (1 << 19), + GW_MYSQL_CAPABILITIES_CONNECT_ATTRS = (1 << 20), + GW_MYSQL_CAPABILITIES_AUTH_LENENC_DATA = (1 << 21), + GW_MYSQL_CAPABILITIES_EXPIRE_PASSWORD = (1 << 22), + GW_MYSQL_CAPABILITIES_SESSION_TRACK = (1 << 23), + GW_MYSQL_CAPABILITIES_DEPRECATE_EOF = (1 << 24), GW_MYSQL_CAPABILITIES_SSL_VERIFY_SERVER_CERT = (1 << 30), GW_MYSQL_CAPABILITIES_REMEMBER_OPTIONS = (1 << 31), - GW_MYSQL_CAPABILITIES_CLIENT = (GW_MYSQL_CAPABILITIES_LONG_PASSWORD | - GW_MYSQL_CAPABILITIES_FOUND_ROWS | - GW_MYSQL_CAPABILITIES_LONG_FLAG | - GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB | - GW_MYSQL_CAPABILITIES_LOCAL_FILES | - GW_MYSQL_CAPABILITIES_PLUGIN_AUTH | - GW_MYSQL_CAPABILITIES_TRANSACTIONS | - GW_MYSQL_CAPABILITIES_PROTOCOL_41 | - GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS | - GW_MYSQL_CAPABILITIES_MULTI_RESULTS | - GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS | - GW_MYSQL_CAPABILITIES_SECURE_CONNECTION), - GW_MYSQL_CAPABILITIES_CLIENT_COMPRESS = (GW_MYSQL_CAPABILITIES_LONG_PASSWORD | - GW_MYSQL_CAPABILITIES_FOUND_ROWS | - GW_MYSQL_CAPABILITIES_LONG_FLAG | - GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB | - GW_MYSQL_CAPABILITIES_LOCAL_FILES | - GW_MYSQL_CAPABILITIES_PLUGIN_AUTH | - GW_MYSQL_CAPABILITIES_TRANSACTIONS | - GW_MYSQL_CAPABILITIES_PROTOCOL_41 | - GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS | - GW_MYSQL_CAPABILITIES_MULTI_RESULTS | - GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS | - GW_MYSQL_CAPABILITIES_COMPRESS - ), + GW_MYSQL_CAPABILITIES_CLIENT = ( + GW_MYSQL_CAPABILITIES_CLIENT_MYSQL | + GW_MYSQL_CAPABILITIES_FOUND_ROWS | + GW_MYSQL_CAPABILITIES_LONG_FLAG | + GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB | + GW_MYSQL_CAPABILITIES_LOCAL_FILES | + GW_MYSQL_CAPABILITIES_PLUGIN_AUTH | + GW_MYSQL_CAPABILITIES_TRANSACTIONS | + GW_MYSQL_CAPABILITIES_PROTOCOL_41 | + GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS | + GW_MYSQL_CAPABILITIES_MULTI_RESULTS | + GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS | + GW_MYSQL_CAPABILITIES_SECURE_CONNECTION), + GW_MYSQL_CAPABILITIES_SERVER = ( + GW_MYSQL_CAPABILITIES_CLIENT_MYSQL | + GW_MYSQL_CAPABILITIES_FOUND_ROWS | + GW_MYSQL_CAPABILITIES_LONG_FLAG | + GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB | + GW_MYSQL_CAPABILITIES_NO_SCHEMA | + GW_MYSQL_CAPABILITIES_ODBC | + GW_MYSQL_CAPABILITIES_LOCAL_FILES | + GW_MYSQL_CAPABILITIES_IGNORE_SPACE | + GW_MYSQL_CAPABILITIES_PROTOCOL_41 | + GW_MYSQL_CAPABILITIES_INTERACTIVE | + GW_MYSQL_CAPABILITIES_IGNORE_SIGPIPE | + GW_MYSQL_CAPABILITIES_TRANSACTIONS | + GW_MYSQL_CAPABILITIES_RESERVED | + GW_MYSQL_CAPABILITIES_SECURE_CONNECTION | + GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS | + GW_MYSQL_CAPABILITIES_MULTI_RESULTS | + GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS | + GW_MYSQL_CAPABILITIES_PLUGIN_AUTH), } gw_mysql_capabilities_t; +/** + * Capabilities supported by MariaDB 10.2 and later, stored in the last 4 bytes + * of the 10 byte filler of the initial handshake packet. + * + * The actual capability bytes use by the server are left shifted by an extra 32 + * bits to get one 64 bit capability that combines the old and new capabilities. + * Since we only use these in the non-shifted form, the definitions declared here + * are right shifted by 32 bytes and can be directly copied into the extra capabilities. + */ +#define MXS_MARIA_CAP_PROGRESS (1 << 0) +#define MXS_MARIA_CAP_COM_MULTI (1 << 1) +#define MXS_MARIA_CAP_STMT_BULK_OPERATIONS (1 << 2) + typedef enum enum_server_command mysql_server_cmd_t; static const mysql_server_cmd_t MYSQL_COM_UNDEFINED = (mysql_server_cmd_t) - 1; @@ -277,6 +302,7 @@ typedef struct uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, created or received */ uint32_t server_capabilities; /*< server capabilities, created or received */ uint32_t client_capabilities; /*< client capabilities, created or received */ + uint32_t extra_capabilities; /*< MariaDB 10.2 capabilities */ unsigned long tid; /*< MySQL Thread ID, in handshake */ unsigned int charset; /*< MySQL character set at connect time */ bool ignore_reply; /*< If the reply should be discarded */ diff --git a/include/maxscale/router.h b/include/maxscale/router.h index 046f28e89..657d66d11 100644 --- a/include/maxscale/router.h +++ b/include/maxscale/router.h @@ -34,7 +34,21 @@ MXS_BEGIN_DECLS * from the @c createInstance function of a router module and subsequently * passing it back to the API functions of the router. */ -typedef void *MXS_ROUTER; +typedef struct mxs_router +{ +} MXS_ROUTER; + +/** + * MXS_ROUTER_SESSION is an opaque type representing the session related + * data of a particular router instance. + * + * MaxScale itself does not do anything with it, except for receiving it + * from the @c newSession function of a router module and subsequently + * passing it back to the API functions of the router. + */ +typedef struct mxs_router_session +{ +} MXS_ROUTER_SESSION; typedef enum error_action { @@ -66,19 +80,19 @@ typedef enum error_action typedef struct mxs_router_object { MXS_ROUTER *(*createInstance)(SERVICE *service, char **options); - void *(*newSession)(MXS_ROUTER *instance, MXS_SESSION *session); - void (*closeSession)(MXS_ROUTER *instance, void *router_session); - void (*freeSession)(MXS_ROUTER *instance, void *router_session); - int32_t (*routeQuery)(MXS_ROUTER *instance, void *router_session, GWBUF *queue); + MXS_ROUTER_SESSION *(*newSession)(MXS_ROUTER *instance, MXS_SESSION *session); + void (*closeSession)(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); + void (*freeSession)(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); + int32_t (*routeQuery)(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); void (*diagnostics)(MXS_ROUTER *instance, DCB *dcb); - void (*clientReply)(MXS_ROUTER* instance, void* router_session, GWBUF* queue, + void (*clientReply)(MXS_ROUTER* instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb); - void (*handleError)(MXS_ROUTER* instance, - void* router_session, - GWBUF* errmsgbuf, - DCB* backend_dcb, + void (*handleError)(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *errmsgbuf, + DCB *backend_dcb, mxs_error_action_t action, - bool* succp); + bool* succp); uint64_t (*getCapabilities)(MXS_ROUTER *instance); void (*destroyInstance)(MXS_ROUTER *instance); } MXS_ROUTER_OBJECT; diff --git a/include/maxscale/spinlock.h b/include/maxscale/spinlock.h index 4c08ec108..c0d46356b 100644 --- a/include/maxscale/spinlock.h +++ b/include/maxscale/spinlock.h @@ -42,7 +42,7 @@ MXS_BEGIN_DECLS */ typedef struct spinlock { - int lock; /*< Is the lock held? */ + volatile int lock;/*< Is the lock held? */ #if SPINLOCK_PROFILE int spins; /*< Number of spins on this lock */ int maxspins; /*< Max no of spins to acquire lock */ diff --git a/query_classifier/qc_sqlite/CMakeLists.txt b/query_classifier/qc_sqlite/CMakeLists.txt index b3d028723..159e792f2 100644 --- a/query_classifier/qc_sqlite/CMakeLists.txt +++ b/query_classifier/qc_sqlite/CMakeLists.txt @@ -18,7 +18,7 @@ include_directories(${MARIADB_CONNECTOR_INCLUDE_DIR}) add_library(qc_sqlite SHARED qc_sqlite.c qc_sqlite3.c builtin_functions.c) add_dependencies(qc_sqlite maxscale_sqlite) -add_definitions(-DMAXSCALE -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_OMIT_ATTACH -DSQLITE_OMIT_REINDEX -DSQLITE_OMIT_AUTOVACUUM -DSQLITE_OMIT_PRAGMA) +add_definitions(-DMAXSCALE -DSQLITE_THREADSAFE=0 -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_OMIT_ATTACH -DSQLITE_OMIT_REINDEX -DSQLITE_OMIT_AUTOVACUUM -DSQLITE_OMIT_PRAGMA) set_target_properties(qc_sqlite PROPERTIES VERSION "1.0.0") set_target_properties(qc_sqlite PROPERTIES LINK_FLAGS -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/qc_sqlite.map) diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index b12bf4d6f..11ebb97a8 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,13 +1,13 @@ add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c config_runtime.c dcb.c filter.c filter.cc externcmd.c paths.c hashtable.c hint.c housekeeper.c load_utils.c log_manager.cc maxscale_pcre2.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c ssl.c mysql_utils.c mysql_binlog.c modulecmd.c ) -target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++) - if(WITH_JEMALLOC) target_link_libraries(maxscale-common ${JEMALLOC_LIBRARIES}) elseif(WITH_TCMALLOC) target_link_libraries(maxscale-common ${TCMALLOC_LIBRARIES}) endif() +target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++) + add_dependencies(maxscale-common pcre2 connector-c) set_target_properties(maxscale-common PROPERTIES VERSION "1.0.0") install_module(maxscale-common core) diff --git a/server/core/gateway.cc b/server/core/gateway.cc index 88c93bba9..cc7d68e2a 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -143,7 +143,7 @@ static struct option long_options[] = static bool syslog_configured = false; static bool maxlog_configured = false; static bool log_to_shm_configured = false; -static int last_signal = 0; +static volatile sig_atomic_t last_signal = 0; static int cnf_preparser(void* data, const char* section, const char* name, const char* value); static void log_flush_shutdown(void); @@ -381,7 +381,7 @@ sigchld_handler (int i) } } -int fatal_handling = 0; +volatile sig_atomic_t fatal_handling = 0; static int signal_set(int sig, void (*handler)(int)); @@ -405,12 +405,12 @@ sigfatal_handler(int i) { void *addrs[128]; - int n, count = backtrace(addrs, 128); + int count = backtrace(addrs, 128); char** symbols = backtrace_symbols(addrs, count); if (symbols) { - for (n = 0; n < count; n++) + for (int n = 0; n < count; n++) { MXS_ALERT(" %s\n", symbols[n]); } @@ -2023,6 +2023,9 @@ int main(int argc, char **argv) unlock_pidfile(); unlink_pidfile(); + ERR_free_strings(); + EVP_cleanup(); + return_main: mxs_log_flush_sync(); @@ -2277,6 +2280,12 @@ bool pid_file_exists() static int write_pid_file() { + if (!mxs_mkdir_all(get_piddir(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) + { + MXS_ERROR("Failed to create PID directory."); + return 1; + } + char logbuf[STRING_BUFFER_SIZE + PATH_MAX]; char pidstr[STRING_BUFFER_SIZE]; diff --git a/server/core/hashtable.c b/server/core/hashtable.c index 3398baffa..1b830eb26 100644 --- a/server/core/hashtable.c +++ b/server/core/hashtable.c @@ -567,10 +567,11 @@ hashtable_read_lock(HASHTABLE *table) while (table->writelock) { spinlock_release(&table->spin); - while (table->writelock) + while (atomic_add(&table->writelock, 1) != 0) { - ; + atomic_add(&table->writelock, -1); } + atomic_add(&table->writelock, -1); spinlock_acquire(&table->spin); } atomic_add(&table->n_readers, 1); @@ -614,10 +615,11 @@ hashtable_write_lock(HASHTABLE *table) spinlock_acquire(&table->spin); do { - while (table->n_readers) + while (atomic_add(&table->n_readers, 1) != 0) { - ; + atomic_add(&table->n_readers, -1); } + atomic_add(&table->n_readers, -1); available = atomic_add(&table->writelock, 1); if (available != 0) { diff --git a/server/core/poll.c b/server/core/poll.c index 634fb8023..e57b93371 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -94,7 +94,7 @@ typedef struct fake_event struct fake_event *next; /*< The next event */ } fake_event_t; -thread_local int thread_id; /**< This thread's ID */ +thread_local int current_thread_id; /**< This thread's ID */ static int *epoll_fd; /*< The epoll file descriptor */ static int next_epoll_fd = 0; /*< Which thread handles the next DCB */ static fake_event_t **fake_events; /*< Thread-specific fake event queue */ @@ -656,9 +656,11 @@ poll_waitevents(void *arg) { struct epoll_event events[MAX_EVENTS]; int i, nfds, timeout_bias = 1; - thread_id = (intptr_t)arg; + current_thread_id = (intptr_t)arg; int poll_spins = 0; + int thread_id = current_thread_id; + thread_data[thread_id].state = THREAD_IDLE; while (1) @@ -1613,7 +1615,7 @@ void poll_send_message(enum poll_message msg, void *data) for (int i = 0; i < nthr; i++) { - if (i != thread_id) + if (i != current_thread_id) { while (poll_msg[i] & msg) { @@ -1628,6 +1630,8 @@ void poll_send_message(enum poll_message msg, void *data) static void poll_check_message() { + int thread_id = current_thread_id; + if (poll_msg[thread_id] & POLL_MSG_CLEAN_PERSISTENT) { SERVER *server = (SERVER*)poll_msg_data; diff --git a/server/modules/filter/CMakeLists.txt b/server/modules/filter/CMakeLists.txt index 7e046f9a2..7a6bcead4 100644 --- a/server/modules/filter/CMakeLists.txt +++ b/server/modules/filter/CMakeLists.txt @@ -10,7 +10,6 @@ add_subdirectory(nullfilter) add_subdirectory(qlafilter) add_subdirectory(regexfilter) add_subdirectory(tee) -add_subdirectory(testfilter) add_subdirectory(topfilter) add_subdirectory(tpmfilter) add_subdirectory(masking) diff --git a/server/modules/filter/cache/cachefiltersession.cc b/server/modules/filter/cache/cachefiltersession.cc index 374295cab..d1612293d 100644 --- a/server/modules/filter/cache/cachefiltersession.cc +++ b/server/modules/filter/cache/cachefiltersession.cc @@ -15,8 +15,9 @@ #include "cachefiltersession.hh" #include #include -#include +#include #include +#include #include "storage.hh" namespace @@ -34,6 +35,108 @@ inline bool cache_max_resultset_size_exceeded(const CACHE_CONFIG& config, uint64 } +namespace +{ + +const char* NON_CACHEABLE_FUNCTIONS[] = +{ + "benchmark", + "connection_id", + "convert_tz", + "curdate", + "current_date", + "current_timestamp", + "curtime", + "database", + "encrypt", + "found_rows", + "get_lock", + "is_free_lock", + "is_used_lock", + "last_insert_id", + "load_file", + "localtime", + "localtimestamp", + "master_pos_wait", + "now", + "rand", + "release_lock", + "session_user", + "sleep", + "sysdate", + "system_user", + "unix_timestamp", + "user", + "uuid", + "uuid_short", +}; + +const char* NON_CACHEABLE_VARIABLES[] = +{ + "current_date", + "current_timestamp", + "localtime", + "localtimestamp", +}; + +const size_t N_NON_CACHEABLE_FUNCTIONS = sizeof(NON_CACHEABLE_FUNCTIONS)/sizeof(NON_CACHEABLE_FUNCTIONS[0]); +const size_t N_NON_CACHEABLE_VARIABLES = sizeof(NON_CACHEABLE_VARIABLES)/sizeof(NON_CACHEABLE_VARIABLES[0]); + +int compare_name(const void* pLeft, const void* pRight) +{ + return strcasecmp((const char*)pLeft, *(const char**)pRight); +} + +inline bool uses_name(const char* zName, const char** pzNames, size_t nNames) +{ + return bsearch(zName, pzNames, nNames, sizeof(const char*), compare_name) != NULL; +} + +bool uses_non_cacheable_function(GWBUF* pPacket) +{ + bool rv = false; + + const QC_FUNCTION_INFO* pInfo; + size_t nInfos; + + qc_get_function_info(pPacket, &pInfo, &nInfos); + + const QC_FUNCTION_INFO* pEnd = pInfo + nInfos; + + while (!rv && (pInfo != pEnd)) + { + rv = uses_name(pInfo->name, NON_CACHEABLE_FUNCTIONS, N_NON_CACHEABLE_FUNCTIONS); + + ++pInfo; + } + + return rv; +} + +bool uses_non_cacheable_variable(GWBUF* pPacket) +{ + bool rv = false; + + const QC_FIELD_INFO* pInfo; + size_t nInfos; + + qc_get_field_info(pPacket, &pInfo, &nInfos); + + const QC_FIELD_INFO* pEnd = pInfo + nInfos; + + while (!rv && (pInfo != pEnd)) + { + rv = uses_name(pInfo->column, NON_CACHEABLE_VARIABLES, N_NON_CACHEABLE_VARIABLES); + + ++pInfo; + } + + return rv; +} + +} + + CacheFilterSession::CacheFilterSession(MXS_SESSION* pSession, Cache* pCache, char* zDefaultDb) : maxscale::FilterSession(pSession) , m_state(CACHE_EXPECTING_NOTHING) @@ -41,6 +144,7 @@ CacheFilterSession::CacheFilterSession(MXS_SESSION* pSession, Cache* pCache, cha , m_zDefaultDb(zDefaultDb) , m_zUseDb(NULL) , m_refreshing(false) + , m_is_read_only(true) { memset(m_key.data, 0, CACHE_KEY_MAXLEN); @@ -129,105 +233,83 @@ int CacheFilterSession::routeQuery(GWBUF* pPacket) break; case MYSQL_COM_QUERY: + if (should_consult_cache(pPacket)) { - // We do not care whether the query was fully parsed or not. - // If a query cannot be fully parsed, the worst thing that can - // happen is that caching is not used, even though it would be - // possible. - if (qc_get_operation(pPacket) == QUERY_OP_SELECT) + if (m_pCache->should_store(m_zDefaultDb, pPacket)) { - MXS_SESSION *session = m_pSession; - - if ((session_is_autocommit(session) && !session_trx_is_active(session)) || - session_trx_is_read_only(session)) + if (m_pCache->should_use(m_pSession)) { - if (m_pCache->should_store(m_zDefaultDb, pPacket)) + GWBUF* pResponse; + cache_result_t result = get_cached_response(pPacket, &pResponse); + + if (CACHE_RESULT_IS_OK(result)) { - if (m_pCache->should_use(m_pSession)) + if (CACHE_RESULT_IS_STALE(result)) { - GWBUF* pResponse; - cache_result_t result = get_cached_response(pPacket, &pResponse); + // The value was found, but it was stale. Now we need to + // figure out whether somebody else is already fetching it. - if (CACHE_RESULT_IS_OK(result)) + if (m_pCache->must_refresh(m_key, this)) { - if (CACHE_RESULT_IS_STALE(result)) + // We were the first ones who hit the stale item. It's + // our responsibility now to fetch it. + if (log_decisions()) { - // The value was found, but it was stale. Now we need to - // figure out whether somebody else is already fetching it. - - if (m_pCache->must_refresh(m_key, this)) - { - // We were the first ones who hit the stale item. It's - // our responsibility now to fetch it. - if (log_decisions()) - { - MXS_NOTICE("Cache data is stale, fetching fresh from server."); - } - - // As we don't use the response it must be freed. - gwbuf_free(pResponse); - - m_refreshing = true; - fetch_from_server = true; - } - else - { - // Somebody is already fetching the new value. So, let's - // use the stale value. No point in hitting the server twice. - if (log_decisions()) - { - MXS_NOTICE("Cache data is stale but returning it, fresh " - "data is being fetched already."); - } - fetch_from_server = false; - } + MXS_NOTICE("Cache data is stale, fetching fresh from server."); } - else - { - if (log_decisions()) - { - MXS_NOTICE("Using fresh data from cache."); - } - fetch_from_server = false; - } - } - else - { + + // As we don't use the response it must be freed. + gwbuf_free(pResponse); + + m_refreshing = true; fetch_from_server = true; } - - if (fetch_from_server) - { - m_state = CACHE_EXPECTING_RESPONSE; - } else { - m_state = CACHE_EXPECTING_NOTHING; - gwbuf_free(pPacket); - DCB *dcb = m_pSession->client_dcb; - - // TODO: This is not ok. Any filters before this filter, will not - // TODO: see this data. - rv = dcb->func.write(dcb, pResponse); + // Somebody is already fetching the new value. So, let's + // use the stale value. No point in hitting the server twice. + if (log_decisions()) + { + MXS_NOTICE("Cache data is stale but returning it, fresh " + "data is being fetched already."); + } + fetch_from_server = false; } } + else + { + if (log_decisions()) + { + MXS_NOTICE("Using fresh data from cache."); + } + fetch_from_server = false; + } } else { - m_state = CACHE_IGNORING_RESPONSE; + fetch_from_server = true; } - } - else - { - if (log_decisions()) + + if (fetch_from_server) { - MXS_NOTICE("autocommit = %s and transaction state %s => Not using or " - "storing to cache.", - session_is_autocommit(m_pSession) ? "ON" : "OFF", - session_trx_state_to_string(session_get_trx_state(m_pSession))); + m_state = CACHE_EXPECTING_RESPONSE; + } + else + { + m_state = CACHE_EXPECTING_NOTHING; + gwbuf_free(pPacket); + DCB *dcb = m_pSession->client_dcb; + + // TODO: This is not ok. Any filters before this filter, will not + // TODO: see this data. + rv = dcb->func.write(dcb, pResponse); } } } + else + { + m_state = CACHE_IGNORING_RESPONSE; + } } break; @@ -664,3 +746,125 @@ void CacheFilterSession::store_result() m_refreshing = false; } } + +/** + * Whether the cache should be consulted. + * + * @param pParam The GWBUF being handled. + * + * @return True, if the cache should be consulted, false otherwise. + */ +bool CacheFilterSession::should_consult_cache(GWBUF* pPacket) +{ + bool consult_cache = false; + + uint32_t type_mask = qc_get_type_mask(pPacket); + + const char* zReason = NULL; + + if (qc_query_is_type(type_mask, QUERY_TYPE_BEGIN_TRX)) + { + // When a transaction is started, we initially assume it is read-only. + m_is_read_only = true; + } + else if (!qc_query_is_type(type_mask, QUERY_TYPE_READ)) + { + // Thereafter, if there's any non-read statement we mark it as non-readonly. + // Note that the state of m_is_read_only is not consulted if there is no + // on-going transaction of if there is an explicitly read-only transaction. + m_is_read_only = false; + } + + if (!session_trx_is_active(m_pSession)) + { + if (log_decisions()) + { + zReason = "no transaction"; + } + consult_cache = true; + } + else if (session_trx_is_read_only(m_pSession)) + { + if (log_decisions()) + { + zReason = "explicitly read-only transaction"; + } + consult_cache = true; + } + else if (m_is_read_only) + { + if (log_decisions()) + { + zReason = "ordinary transaction that has so far been read-only"; + } + consult_cache = true; + } + else + { + if (log_decisions()) + { + zReason = "ordinary transaction with non-read statements"; + } + } + + if (consult_cache) + { + if (qc_get_operation(pPacket) == QUERY_OP_SELECT) + { + if (qc_query_is_type(type_mask, QUERY_TYPE_USERVAR_READ)) + { + consult_cache = false; + zReason = "user variables are read"; + } + else if (qc_query_is_type(type_mask, QUERY_TYPE_SYSVAR_READ)) + { + consult_cache = false; + zReason = "system variables are read"; + } + else if (uses_non_cacheable_function(pPacket)) + { + consult_cache = false; + zReason = "uses non-cacheable function"; + } + else if (uses_non_cacheable_variable(pPacket)) + { + consult_cache = false; + zReason = "uses non-cacheable variable"; + } + } + else + { + consult_cache = false; + zReason = "statement is not SELECT"; + } + } + + if (log_decisions()) + { + char* pSql; + int length; + const int max_length = 40; + + // At this point we know it's a COM_QUERY and that the buffer is contiguous + modutil_extract_SQL(pPacket, &pSql, &length); + + const char* zFormat; + + if (length <= max_length) + { + zFormat = "%s, \"%.*s\", %s."; + } + else + { + zFormat = "%s, \"%.*s...\", %s."; + length = max_length - 3; // strlen("..."); + } + + const char* zDecision = (consult_cache ? "CONSULT" : "IGNORE "); + + ss_dassert(zReason); + MXS_NOTICE(zFormat, zDecision, length, pSql, zReason); + } + + return consult_cache; +} diff --git a/server/modules/filter/cache/cachefiltersession.hh b/server/modules/filter/cache/cachefiltersession.hh index 8ec445d7c..29631a00c 100644 --- a/server/modules/filter/cache/cachefiltersession.hh +++ b/server/modules/filter/cache/cachefiltersession.hh @@ -105,6 +105,8 @@ private: void store_result(); + bool should_consult_cache(GWBUF* pPacket); + private: CacheFilterSession(MXS_SESSION* pSession, Cache* pCache, char* zDefaultDb); @@ -119,5 +121,6 @@ private: char* m_zDefaultDb; /**< The default database. */ char* m_zUseDb; /**< Pending default database. Needs server response. */ bool m_refreshing; /**< Whether the session is updating a stale cache entry. */ + bool m_is_read_only;/**< Whether the current trx has been read-only in pratice. */ }; diff --git a/server/modules/filter/maxrows/maxrows.c b/server/modules/filter/maxrows/maxrows.c index 1ca42d4cf..0edac3543 100644 --- a/server/modules/filter/maxrows/maxrows.c +++ b/server/modules/filter/maxrows/maxrows.c @@ -147,6 +147,7 @@ typedef struct maxrows_response_state size_t n_fields; /**< How many fields we have received, <= n_totalfields. */ size_t n_rows; /**< How many rows we have received. */ size_t offset; /**< Where we are in the response buffer. */ + size_t rows_offset; /**< Offset to first row in result set */ } MAXROWS_RESPONSE_STATE; static void maxrows_response_state_reset(MAXROWS_RESPONSE_STATE *state); @@ -174,7 +175,7 @@ static int handle_ignoring_response(MAXROWS_SESSION_DATA *csdata); static bool process_params(char **options, MXS_CONFIG_PARAMETER *params, MAXROWS_CONFIG* config); static int send_upstream(MAXROWS_SESSION_DATA *csdata); -static int send_ok_upstream(MAXROWS_SESSION_DATA *csdata); +static int send_eof_upstream(MAXROWS_SESSION_DATA *csdata, size_t offset); /* API BEGIN */ @@ -438,6 +439,7 @@ static void maxrows_response_state_reset(MAXROWS_RESPONSE_STATE *state) state->n_fields = 0; state->n_rows = 0; state->offset = 0; + state->rows_offset = 0; } /** @@ -511,6 +513,15 @@ static int handle_expecting_fields(MAXROWS_SESSION_DATA *csdata) { case 0xfe: // EOF, the one after the fields. csdata->res.offset += packetlen; + + /* Now set the offset to the first resultset + * this could be used for empty response handler + */ + if (!csdata->res.rows_offset) + { + csdata->res.rows_offset = csdata->res.offset; + } + csdata->state = MAXROWS_EXPECTING_ROWS; rv = handle_rows(csdata); break; @@ -599,7 +610,7 @@ static int handle_expecting_response(MAXROWS_SESSION_DATA *csdata) if (csdata->discard_resultset) { - rv = send_ok_upstream(csdata); + rv = send_eof_upstream(csdata, csdata->res.rows_offset); csdata->state = MAXROWS_EXPECTING_NOTHING; } else @@ -746,7 +757,7 @@ static int handle_rows(MAXROWS_SESSION_DATA *csdata) // Send data in buffer or empty resultset if (csdata->discard_resultset) { - rv = send_ok_upstream(csdata); + rv = send_eof_upstream(csdata, csdata->res.rows_offset); } else { @@ -779,7 +790,7 @@ static int handle_rows(MAXROWS_SESSION_DATA *csdata) if (packetlen < MYSQL_EOF_PACKET_LEN) { MXS_ERROR("EOF packet has size of %lu instead of %d", packetlen, MYSQL_EOF_PACKET_LEN); - rv = send_ok_upstream(csdata); + rv = send_eof_upstream(csdata, csdata->res.rows_offset); csdata->state = MAXROWS_EXPECTING_NOTHING; break; } @@ -800,7 +811,7 @@ static int handle_rows(MAXROWS_SESSION_DATA *csdata) // Discard data or send data if (csdata->discard_resultset) { - rv = send_ok_upstream(csdata); + rv = send_eof_upstream(csdata, csdata->res.rows_offset); } else { @@ -897,23 +908,65 @@ static int send_upstream(MAXROWS_SESSION_DATA *csdata) } /** - * Send OK packet data upstream. + * Send upstream the Response Buffer up to columns def in response + * including its EOF of the first result set + * An EOF packet for empty result set with no MULTI flags is added + * at the end. * - * @param csdata Session data + * @param csdata Session data + * @param offset The offset to server reply pointing to + * next byte after column definitions EOF + * of the first result set. * * @return Whatever the upstream returns. */ -static int send_ok_upstream(MAXROWS_SESSION_DATA *csdata) +static int send_eof_upstream(MAXROWS_SESSION_DATA *csdata, size_t offset) { - /* Note: sequence id is always 01 (4th byte) */ - uint8_t ok[MYSQL_OK_PACKET_MIN_LEN] = {07, 00, 00, 01, 00, 00, 00, 02, 00, 00, 00}; - GWBUF *packet = gwbuf_alloc(MYSQL_OK_PACKET_MIN_LEN); - uint8_t *ptr = GWBUF_DATA(packet); - memcpy(ptr, &ok, MYSQL_OK_PACKET_MIN_LEN); + int rv = -1; + /* Sequence byte is #3 */ + uint8_t eof[MYSQL_EOF_PACKET_LEN] = {05, 00, 00, 01, 0xfe, 00, 00, 02, 00}; + GWBUF *new_pkt = NULL; ss_dassert(csdata->res.data != NULL); - int rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, packet); + /* Data to send + added EOF */ + uint8_t *new_result = MXS_MALLOC(offset + MYSQL_EOF_PACKET_LEN); + + if (new_result) + { + /* Get contiguous data from beginning to specified offset */ + gwbuf_copy_data(csdata->res.data, 0, offset, new_result); + + /* Increment sequence number for the EOF being added for empty resultset: + * last one if found in EOF terminating column def + */ + eof[3] = new_result[offset - (MYSQL_EOF_PACKET_LEN - 3)] + 1; + + /* Copy EOF data */ + memcpy(new_result + offset, &eof, MYSQL_EOF_PACKET_LEN); + + /* Create new packet */ + new_pkt = gwbuf_alloc_and_load(offset + MYSQL_EOF_PACKET_LEN, new_result); + + /* Free intermediate data */ + MXS_FREE(new_result); + + if (new_pkt) + { + /* new_pkt will be freed by write routine */ + rv = csdata->up.clientReply(csdata->up.instance, csdata->up.session, new_pkt); + } + } + + /* Abort client connection */ + if (!(new_result && new_pkt)) + { + /* Abort client connection */ + poll_fake_hangup_event(csdata->session->client_dcb); + rv = 0; + } + + /* Free full input buffer */ gwbuf_free(csdata->res.data); csdata->res.data = NULL; diff --git a/server/modules/filter/testfilter/CMakeLists.txt b/server/modules/filter/testfilter/CMakeLists.txt deleted file mode 100644 index 46723361b..000000000 --- a/server/modules/filter/testfilter/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_library(testfilter SHARED testfilter.c) -target_link_libraries(testfilter maxscale-common) -set_target_properties(testfilter PROPERTIES VERSION "1.0.0") -install_module(testfilter core) diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c index 9caa297f8..8d1b01901 100644 --- a/server/modules/monitor/mysqlmon/mysql_mon.c +++ b/server/modules/monitor/mysqlmon/mysql_mon.c @@ -127,7 +127,7 @@ MXS_MODULE* MXS_CREATE_MODULE() {"multimaster", MXS_MODULE_PARAM_BOOL, "false"}, {"failover", MXS_MODULE_PARAM_BOOL, "false"}, {"failcount", MXS_MODULE_PARAM_COUNT, "5"}, - {"failover_recovery", MXS_MODULE_PARAM_BOOL, "false"}, + {"failover_recovery", MXS_MODULE_PARAM_BOOL, "true"}, { "script", MXS_MODULE_PARAM_PATH, @@ -973,7 +973,7 @@ bool failover_required(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *db) candidates++; MYSQL_SERVER_INFO *server_info = hashtable_fetch(handle->server_info, db->server->unique_name); - if (server_info->read_only || candidates > 1) + if (server_info->read_only || server_info->slave_configured || candidates > 1) { return false; } diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index 49a2610f8..bfde80a5e 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -8,6 +8,3 @@ add_subdirectory(maxscaled) add_subdirectory(MySQL) add_subdirectory(telnetd) -if(BUILD_TESTS) - add_subdirectory(testprotocol) -endif() diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index f88af3d3a..bedd8eb0e 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -189,12 +189,10 @@ static int gw_create_backend_connection(DCB *backend_dcb, /** Copy client flags to backend protocol */ if (backend_dcb->session->client_dcb->protocol) { - /** Copy client flags to backend protocol */ - protocol->client_capabilities = - ((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->client_capabilities; - /** Copy client charset to backend protocol */ - protocol->charset = - ((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->charset; + MySQLProtocol *client = (MySQLProtocol*)backend_dcb->session->client_dcb->protocol; + protocol->client_capabilities = client->client_capabilities; + protocol->charset = client->charset; + protocol->extra_capabilities = client->extra_capabilities; } else { diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index 1b4269e8a..bb13ceaf1 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -224,16 +224,25 @@ int MySQLSendHandshake(DCB* dcb) uint8_t mysql_server_language = 8; uint8_t mysql_server_status[2]; uint8_t mysql_scramble_len = 21; - uint8_t mysql_filler_ten[10]; + uint8_t mysql_filler_ten[10] = {}; /* uint8_t mysql_last_byte = 0x00; not needed */ char server_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1] = ""; char *version_string; int len_version_string = 0; int id_num; + bool is_maria = false; + if (dcb->service->dbref) { mysql_server_language = dcb->service->dbref->server->charset; + + if (dcb->service->dbref->server->server_string && + strstr(dcb->service->dbref->server->server_string, "10.2.")) + { + /** The backend servers support the extended capabilities */ + is_maria = true; + } } MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol); @@ -256,9 +265,15 @@ int MySQLSendHandshake(DCB* dcb) // copy back to the caller memcpy(protocol->scramble, server_scramble, GW_MYSQL_SCRAMBLE_SIZE); - // fill the handshake packet - - memset(mysql_filler_ten, 0x00, sizeof(mysql_filler_ten)); + if (is_maria) + { + /** + * The new 10.2 capability flags are stored in the last 4 bytes of the + * 10 byte filler block. + */ + uint32_t new_flags = MXS_MARIA_CAP_STMT_BULK_OPERATIONS; + memcpy(mysql_filler_ten + 6, &new_flags, sizeof(new_flags)); + } // thread id, now put thePID id_num = getpid() + dcb->fd; @@ -324,11 +339,19 @@ int MySQLSendHandshake(DCB* dcb) mysql_handshake_payload++; // write server capabilities part one - mysql_server_capabilities_one[0] = GW_MYSQL_SERVER_CAPABILITIES_BYTE1; - mysql_server_capabilities_one[1] = GW_MYSQL_SERVER_CAPABILITIES_BYTE2; + mysql_server_capabilities_one[0] = (uint8_t)GW_MYSQL_CAPABILITIES_SERVER; + mysql_server_capabilities_one[1] = (uint8_t)(GW_MYSQL_CAPABILITIES_SERVER >> 8); + // Check that we match the old values + ss_dassert(mysql_server_capabilities_one[0] = 0xff); + ss_dassert(mysql_server_capabilities_one[1] = 0xf7); - mysql_server_capabilities_one[0] &= ~(int)GW_MYSQL_CAPABILITIES_COMPRESS; + if (is_maria) + { + /** A MariaDB 10.2 server doesn't send the CLIENT_MYSQL capability + * to signal that it supports extended capabilities */ + mysql_server_capabilities_one[0] &= ~(uint8_t)GW_MYSQL_CAPABILITIES_CLIENT_MYSQL; + } if (ssl_required_by_dcb(dcb)) { @@ -349,8 +372,13 @@ int MySQLSendHandshake(DCB* dcb) mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_status); //write server capabilities part two - mysql_server_capabilities_two[0] = 15; - mysql_server_capabilities_two[1] = 128; + mysql_server_capabilities_two[0] = (uint8_t)(GW_MYSQL_CAPABILITIES_SERVER >> 16); + mysql_server_capabilities_two[1] = (uint8_t)(GW_MYSQL_CAPABILITIES_SERVER >> 24); + + // Check that we match the old values + ss_dassert(mysql_server_capabilities_two[0] == 15); + /** NOTE: pre-2.1 versions sent the fourth byte of the capabilities as + the value 128 even though there's no such capability. */ memcpy(mysql_handshake_payload, mysql_server_capabilities_two, sizeof(mysql_server_capabilities_two)); mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_two); @@ -531,6 +559,13 @@ static void store_client_information(DCB *dcb, GWBUF *buffer) proto->client_capabilities = gw_mysql_get_byte4(data + MYSQL_CLIENT_CAP_OFFSET); proto->charset = data[MYSQL_CHARSET_OFFSET]; + /** MariaDB 10.2 compatible clients don't set the first bit to signal that + * there are extra capabilities stored in the last 4 bytes of the 23 byte filler. */ + if ((proto->client_capabilities & GW_MYSQL_CAPABILITIES_CLIENT_MYSQL) == 0) + { + proto->extra_capabilities = gw_mysql_get_byte4(data + MARIADB_CAP_OFFSET); + } + if (len > MYSQL_AUTH_PACKET_BASE_SIZE) { strcpy(ses->user, (char*)data + MYSQL_AUTH_PACKET_BASE_SIZE); @@ -825,7 +860,12 @@ static bool process_client_commands(DCB* dcb, int bytes_available, GWBUF** buffe } MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol; - proto->current_command = cmd; + if (dcb->protocol_packet_length - MYSQL_HEADER_LEN != GW_MYSQL_MAX_PACKET_LEN) + { + /** We're processing the first packet of a command */ + proto->current_command = cmd; + } + dcb->protocol_packet_length = pktlen + MYSQL_HEADER_LEN; dcb->protocol_bytes_processed = 0; } diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index 208e0adb9..8103ff9aa 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -103,6 +103,7 @@ MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd) p->protocol_command.scom_nresponse_packets = 0; p->protocol_command.scom_nbytes_to_read = 0; p->stored_query = NULL; + p->extra_capabilities = 0; #if defined(SS_DEBUG) p->protocol_chk_top = CHK_NUM_PROTOCOL; p->protocol_chk_tail = CHK_NUM_PROTOCOL; @@ -1364,8 +1365,12 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) payload++; - // 23 bytes of 0 - payload += 23; + // 19 filler bytes of 0 + payload += 19; + + // Either MariaDB 10.2 extra capabilities or 4 bytes filler + memcpy(payload, &conn->extra_capabilities, sizeof(conn->extra_capabilities)); + payload += 4; if (dcb->server->server_ssl && dcb->ssl_state != SSL_ESTABLISHED) { diff --git a/server/modules/protocol/testprotocol/CMakeLists.txt b/server/modules/protocol/testprotocol/CMakeLists.txt deleted file mode 100644 index 2e6b401e0..000000000 --- a/server/modules/protocol/testprotocol/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_library(testprotocol SHARED testprotocol.c) -set_target_properties(testprotocol PROPERTIES VERSION "1.0.0") -install_module(testprotocol core) diff --git a/server/modules/routing/CMakeLists.txt b/server/modules/routing/CMakeLists.txt index 74d00e3ee..0060edf8f 100644 --- a/server/modules/routing/CMakeLists.txt +++ b/server/modules/routing/CMakeLists.txt @@ -12,6 +12,4 @@ add_subdirectory(readconnroute) add_subdirectory(readwritesplit) add_subdirectory(schemarouter) -if(BUILD_TESTS) - add_subdirectory(testroute) -endif() + diff --git a/server/modules/routing/avrorouter/avro.c b/server/modules/routing/avrorouter/avro.c index 5fa9fd04b..e87558275 100644 --- a/server/modules/routing/avrorouter/avro.c +++ b/server/modules/routing/avrorouter/avro.c @@ -72,14 +72,14 @@ static const char* alter_table_regex = /* The router entry points */ static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *router_session); -static void freeSession(MXS_ROUTER *instance, void *router_session); -static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); -static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, +static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb); -static void errorReply(MXS_ROUTER *instance, void *router_session, GWBUF *message, +static void errorReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *message, DCB *backend_dcb, mxs_error_action_t action, bool *succp); static uint64_t getCapabilities(MXS_ROUTER* instance); extern int MaxScaleUptime(); @@ -630,7 +630,7 @@ createInstance(SERVICE *service, char **options) * @param session The session itself * @return Session specific data for this session */ -static void * +static MXS_ROUTER_SESSION * newSession(MXS_ROUTER *instance, MXS_SESSION *session) { AVRO_INSTANCE *inst = (AVRO_INSTANCE *) instance; @@ -703,7 +703,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session) * @param router_cli_ses The particular session to free * */ -static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses) +static void freeSession(MXS_ROUTER* router_instance, MXS_ROUTER_SESSION* router_client_ses) { AVRO_INSTANCE *router = (AVRO_INSTANCE *) router_instance; AVRO_CLIENT *client = (AVRO_CLIENT *) router_client_ses; @@ -752,7 +752,7 @@ static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses) * @param instance The router instance data * @param router_session The session being closed */ -static void closeSession(MXS_ROUTER *instance, void *router_session) +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) { AVRO_INSTANCE *router = (AVRO_INSTANCE *) instance; AVRO_CLIENT *client = (AVRO_CLIENT *) router_session; @@ -784,7 +784,7 @@ static void closeSession(MXS_ROUTER *instance, void *router_session) * @return 1 on success, 0 on error */ static int -routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue) +routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue) { AVRO_INSTANCE *router = (AVRO_INSTANCE *) instance; AVRO_CLIENT *client = (AVRO_CLIENT *) router_session; @@ -960,7 +960,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) * @param queue The GWBUF with reply data */ static void -clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb) +clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb) { /** We should never end up here */ ss_dassert(false); @@ -1002,7 +1002,7 @@ extract_message(GWBUF *errpkt) * */ static void -errorReply(MXS_ROUTER *instance, void *router_session, GWBUF *message, DCB *backend_dcb, +errorReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *message, DCB *backend_dcb, mxs_error_action_t action, bool *succp) { diff --git a/server/modules/routing/avrorouter/avro_rbr.c b/server/modules/routing/avrorouter/avro_rbr.c index 7a1aed890..24d640f50 100644 --- a/server/modules/routing/avrorouter/avro_rbr.c +++ b/server/modules/routing/avrorouter/avro_rbr.c @@ -30,7 +30,7 @@ static bool warn_large_enumset = false; /**< Remove when support for ENUM/SET va uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value_t *record, uint8_t *ptr, - uint8_t *columns_present); + uint8_t *columns_present, uint8_t *end); void notify_all_clients(AVRO_INSTANCE *router); void add_used_table(AVRO_INSTANCE* router, const char* table); @@ -309,9 +309,10 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr) while (ptr - start < hdr->event_size - BINLOG_EVENT_HDR_LEN) { /** Add the current GTID and timestamp */ + uint8_t *end = ptr + hdr->event_size; int event_type = get_event_type(hdr->event_type); prepare_record(router, hdr, event_type, &record); - ptr = process_row_event_data(map, create, &record, ptr, col_present); + ptr = process_row_event_data(map, create, &record, ptr, col_present, end); avro_file_writer_append_value(table->avro_file, &record); /** Update rows events have the before and after images of the @@ -320,7 +321,7 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr) if (event_type == UPDATE_EVENT) { prepare_record(router, hdr, UPDATE_EVENT_AFTER, &record); - ptr = process_row_event_data(map, create, &record, ptr, col_present); + ptr = process_row_event_data(map, create, &record, ptr, col_present, end); avro_file_writer_append_value(table->avro_file, &record); } @@ -497,7 +498,7 @@ int get_metadata_len(uint8_t type) * @return Pointer to the first byte after the current row event */ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value_t *record, - uint8_t *ptr, uint8_t *columns_present) + uint8_t *ptr, uint8_t *columns_present, uint8_t *end) { int npresent = 0; avro_value_t field; @@ -507,10 +508,12 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value /** BIT type values use the extra bits in the row event header */ int extra_bits = (((ncolumns + 7) / 8) * 8) - ncolumns; + ss_dassert(ptr < end); /** Store the null value bitmap */ uint8_t *null_bitmap = ptr; ptr += (ncolumns + 7) / 8; + ss_dassert(ptr < end); for (long i = 0; i < map->columns && npresent < ncolumns; i++) { @@ -544,6 +547,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value } avro_value_set_string(&field, strval); ptr += bytes; + ss_dassert(ptr < end); } else { @@ -553,6 +557,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value str[bytes] = '\0'; avro_value_set_string(&field, str); ptr += bytes + 1; + ss_dassert(ptr < end); } } else if (column_is_bit(map->column_types[i])) @@ -572,21 +577,36 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value } avro_value_set_int(&field, value); ptr += bytes; + ss_dassert(ptr < end); } else if (column_is_decimal(map->column_types[i])) { double f_value = 0.0; ptr += unpack_decimal_field(ptr, metadata + metadata_offset, &f_value); avro_value_set_double(&field, f_value); + ss_dassert(ptr < end); } else if (column_is_variable_string(map->column_types[i])) { size_t sz; - char *str = mxs_lestr_consume(&ptr, &sz); + int bytes = metadata[metadata_offset] | metadata[metadata_offset + 1] << 8; + if (bytes > 255) + { + sz = gw_mysql_get_byte2(ptr); + ptr += 2; + } + else + { + sz = *ptr; + ptr++; + } + char buf[sz + 1]; - memcpy(buf, str, sz); + memcpy(buf, ptr, sz); buf[sz] = '\0'; + ptr += sz; avro_value_set_string(&field, buf); + ss_dassert(ptr < end); } else if (column_is_blob(map->column_types[i])) { @@ -596,6 +616,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value ptr += bytes; avro_value_set_bytes(&field, ptr, len); ptr += len; + ss_dassert(ptr < end); } else if (column_is_temporal(map->column_types[i])) { @@ -604,6 +625,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value ptr += unpack_temporal_value(map->column_types[i], ptr, &metadata[metadata_offset], &tm); format_temporal_value(buf, sizeof(buf), map->column_types[i], &tm); avro_value_set_string(&field, buf); + ss_dassert(ptr < end); } /** All numeric types (INT, LONG, FLOAT etc.) */ else @@ -613,6 +635,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value ptr += unpack_numeric_field(ptr, map->column_types[i], &metadata[metadata_offset], lval); set_numeric_field_value(&field, map->column_types[i], &metadata[metadata_offset], lval); + ss_dassert(ptr < end); } ss_dassert(metadata_offset <= map->column_metadata_size); metadata_offset += get_metadata_len(map->column_types[i]); diff --git a/server/modules/routing/avrorouter/avro_schema.c b/server/modules/routing/avrorouter/avro_schema.c index 4ca87efa2..4e9458074 100644 --- a/server/modules/routing/avrorouter/avro_schema.c +++ b/server/modules/routing/avrorouter/avro_schema.c @@ -449,6 +449,10 @@ static const char *extract_field_name(const char* ptr, char* dest, size_t size) while (*ptr && (isspace(*ptr) || (bt = *ptr == '`'))) { ptr++; + if (bt) + { + break; + } } if (strncasecmp(ptr, "constraint", 10) == 0 || strncasecmp(ptr, "index", 5) == 0 || @@ -481,11 +485,6 @@ static const char *extract_field_name(const char* ptr, char* dest, size_t size) /** Valid identifier */ size_t bytes = ptr - start; - if (bt) - { - bytes--; - } - memcpy(dest, start, bytes); dest[bytes] = '\0'; diff --git a/server/modules/routing/binlogrouter/blr.c b/server/modules/routing/binlogrouter/blr.c index 34a52fdda..f952040f4 100644 --- a/server/modules/routing/binlogrouter/blr.c +++ b/server/modules/routing/binlogrouter/blr.c @@ -90,21 +90,21 @@ /* The router entry points */ static MXS_ROUTER *createInstance(SERVICE *service, char **options); static void free_instance(ROUTER_INSTANCE *instance); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *router_session); -static void freeSession(MXS_ROUTER *instance, void *router_session); -static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue); -static void diagnostics(MXS_ROUTER *instance, DCB *dcb); -static void clientReply(MXS_ROUTER *instance, - void *router_session, - GWBUF *queue, - DCB *backend_dcb); -static void errorReply(MXS_ROUTER *instance, - void *router_session, - GWBUF *message, - DCB *backend_dcb, - mxs_error_action_t action, - bool *succp); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); +static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static void clientReply(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *queue, + DCB *backend_dcb); +static void errorReply(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *message, + DCB *backend_dcb, + mxs_error_action_t action, + bool *succp); static uint64_t getCapabilities(MXS_ROUTER* instance); static int blr_handler_config(void *userdata, const char *section, const char *name, const char *value); @@ -952,7 +952,7 @@ free_instance(ROUTER_INSTANCE *instance) * @param session The session itself * @return Session specific data for this session */ -static void * +static MXS_ROUTER_SESSION * newSession(MXS_ROUTER *instance, MXS_SESSION *session) { ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; @@ -1022,7 +1022,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session) * */ static void freeSession(MXS_ROUTER* router_instance, - void* router_client_ses) + MXS_ROUTER_SESSION* router_client_ses) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_instance; ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_client_ses; @@ -1090,7 +1090,7 @@ static void freeSession(MXS_ROUTER* router_instance, * @param router_session The session being closed */ static void -closeSession(MXS_ROUTER *instance, void *router_session) +closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session; @@ -1176,7 +1176,7 @@ closeSession(MXS_ROUTER *instance, void *router_session) * @return The number of bytes sent */ static int -routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue) +routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session; @@ -1680,7 +1680,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) * @param queue The GWBUF with reply data */ static void -clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb) +clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; @@ -1726,7 +1726,7 @@ extract_message(GWBUF *errpkt) */ static void errorReply(MXS_ROUTER *instance, - void *router_session, + MXS_ROUTER_SESSION *router_session, GWBUF *message, DCB *backend_dcb, mxs_error_action_t action, diff --git a/server/modules/routing/binlogrouter/blr_file.c b/server/modules/routing/binlogrouter/blr_file.c index c4d80905d..35a8bf077 100644 --- a/server/modules/routing/binlogrouter/blr_file.c +++ b/server/modules/routing/binlogrouter/blr_file.c @@ -2463,6 +2463,7 @@ blr_file_write_master_config(ROUTER_INSTANCE *router, char *error) if (chmod(tmp_file, S_IRUSR | S_IWUSR) < 0) { + fclose(config_file); snprintf(error, BINLOG_ERROR_MSG_LEN, "%s, errno %u", strerror_r(errno, err_msg, sizeof(err_msg)), errno); return 2; diff --git a/server/modules/routing/cli/cli.c b/server/modules/routing/cli/cli.c index e8bcafaf1..15a003083 100644 --- a/server/modules/routing/cli/cli.c +++ b/server/modules/routing/cli/cli.c @@ -44,11 +44,11 @@ /* The router entry points */ static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *router_session); -static void freeSession(MXS_ROUTER *instance, void *router_session); -static int execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue); -static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); +static void diagnostics(MXS_ROUTER *instance, DCB *dcb); static uint64_t getCapabilities(MXS_ROUTER* instance); extern int execute_cmd(CLI_SESSION *cli); @@ -156,7 +156,7 @@ createInstance(SERVICE *service, char **options) * @param session The session itself * @return Session specific data for this session */ -static void * +static MXS_ROUTER_SESSION * newSession(MXS_ROUTER *instance, MXS_SESSION *session) { CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; @@ -188,7 +188,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session) * @param router_session The session being closed */ static void -closeSession(MXS_ROUTER *instance, void *router_session) +closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) { CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; CLI_SESSION *session = (CLI_SESSION *)router_session; @@ -224,9 +224,8 @@ closeSession(MXS_ROUTER *instance, void *router_session) * @param router_instance The router session * @param router_client_session The router session as returned from newSession */ -static void freeSession( - MXS_ROUTER* router_instance, - void* router_client_session) +static void freeSession(MXS_ROUTER* router_instance, + MXS_ROUTER_SESSION* router_client_session) { MXS_FREE(router_client_session); return; @@ -243,7 +242,7 @@ static void freeSession( * @return The number of bytes sent */ static int -execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue) +execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue) { CLI_SESSION *session = (CLI_SESSION *)router_session; diff --git a/server/modules/routing/debugcli/debugcli.c b/server/modules/routing/debugcli/debugcli.c index e1fc2d440..fda461bbc 100644 --- a/server/modules/routing/debugcli/debugcli.c +++ b/server/modules/routing/debugcli/debugcli.c @@ -43,11 +43,11 @@ /* The router entry points */ static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *router_session); -static void freeSession(MXS_ROUTER *instance, void *router_session); -static int execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue); -static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); +static void diagnostics(MXS_ROUTER *instance, DCB *dcb); static uint64_t getCapabilities(MXS_ROUTER* instance); extern int execute_cmd(CLI_SESSION *cli); @@ -147,7 +147,7 @@ createInstance(SERVICE *service, char **options) * @param session The session itself * @return Session specific data for this session */ -static void * +static MXS_ROUTER_SESSION * newSession(MXS_ROUTER *instance, MXS_SESSION *session) { CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; @@ -182,7 +182,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session) * @param router_session The session being closed */ static void -closeSession(MXS_ROUTER *instance, void *router_session) +closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) { CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; CLI_SESSION *session = (CLI_SESSION *)router_session; @@ -218,9 +218,8 @@ closeSession(MXS_ROUTER *instance, void *router_session) * @param router_instance The router session * @param router_client_session The router session as returned from newSession */ -static void freeSession( - MXS_ROUTER* router_instance, - void* router_client_session) +static void freeSession(MXS_ROUTER* router_instance, + MXS_ROUTER_SESSION* router_client_session) { MXS_FREE(router_client_session); return; @@ -237,7 +236,7 @@ static void freeSession( * @return The number of bytes sent */ static int -execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue) +execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue) { CLI_SESSION *session = (CLI_SESSION *)router_session; diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 8d93f0544..63c84e3bf 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -65,18 +65,18 @@ static int handle_url(INFO_INSTANCE *instance, INFO_SESSION *router_session, GWB /* The router entry points */ static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *router_session); -static void freeSession(MXS_ROUTER *instance, void *router_session); -static int execute(MXS_ROUTER *instance, void *router_session, GWBUF *queue); -static void diagnostics(MXS_ROUTER *instance, DCB *dcb); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); +static void diagnostics(MXS_ROUTER *instance, DCB *dcb); static uint64_t getCapabilities(MXS_ROUTER* instance); -static void handleError(MXS_ROUTER *instance, - void *router_session, - GWBUF *errbuf, - DCB *backend_dcb, - mxs_error_action_t action, - bool *succp); +static void handleError(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *errbuf, + DCB *backend_dcb, + mxs_error_action_t action, + bool *succp); static SPINLOCK instlock; static INFO_INSTANCE *instances; @@ -180,7 +180,7 @@ createInstance(SERVICE *service, char **options) * @param session The session itself * @return Session specific data for this session */ -static void * +static MXS_ROUTER_SESSION * newSession(MXS_ROUTER *instance, MXS_SESSION *session) { INFO_INSTANCE *inst = (INFO_INSTANCE *)instance; @@ -212,7 +212,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session) * @param router_session The session being closed */ static void -closeSession(MXS_ROUTER *instance, void *router_session) +closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) { INFO_INSTANCE *inst = (INFO_INSTANCE *)instance; INFO_SESSION *session = (INFO_SESSION *)router_session; @@ -249,7 +249,7 @@ closeSession(MXS_ROUTER *instance, void *router_session) * @param router_client_session The router session as returned from newSession */ static void freeSession(MXS_ROUTER* router_instance, - void* router_client_session) + MXS_ROUTER_SESSION* router_client_session) { MXS_FREE(router_client_session); return; @@ -268,12 +268,12 @@ static void freeSession(MXS_ROUTER* router_instance, * @param succp Result of action: true iff router can continue * */ -static void handleError(MXS_ROUTER *instance, - void *router_session, - GWBUF *errbuf, - DCB *backend_dcb, +static void handleError(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *errbuf, + DCB *backend_dcb, mxs_error_action_t action, - bool *succp) + bool *succp) { DCB *client_dcb; @@ -316,7 +316,7 @@ static void handleError(MXS_ROUTER *instance, * @return The number of bytes sent */ static int -execute(MXS_ROUTER *rinstance, void *router_session, GWBUF *queue) +execute(MXS_ROUTER *rinstance, MXS_ROUTER_SESSION *router_session, GWBUF *queue) { INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance; INFO_SESSION *session = (INFO_SESSION *)router_session; diff --git a/server/modules/routing/readconnroute/readconnroute.c b/server/modules/routing/readconnroute/readconnroute.c index fa1447338..7c1942599 100644 --- a/server/modules/routing/readconnroute/readconnroute.c +++ b/server/modules/routing/readconnroute/readconnroute.c @@ -89,14 +89,14 @@ /* The router entry points */ static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *router_session); -static void freeSession(MXS_ROUTER *instance, void *router_session); -static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session); +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); -static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, +static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb); -static void handleError(MXS_ROUTER *instance, void *router_session, GWBUF *errbuf, +static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *errbuf, DCB *problem_dcb, mxs_error_action_t action, bool *succp); static uint64_t getCapabilities(MXS_ROUTER* instance); static bool rses_begin_locked_router_action(ROUTER_CLIENT_SES* rses); @@ -257,7 +257,7 @@ createInstance(SERVICE *service, char **options) * @param session The session itself * @return Session specific data for this session */ -static void * +static MXS_ROUTER_SESSION * newSession(MXS_ROUTER *instance, MXS_SESSION *session) { ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; @@ -450,7 +450,7 @@ newSession(MXS_ROUTER *instance, MXS_SESSION *session) * @details (write detailed description here) * */ -static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses) +static void freeSession(MXS_ROUTER* router_instance, MXS_ROUTER_SESSION* router_client_ses) { ROUTER_INSTANCE* router = (ROUTER_INSTANCE *) router_instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *) router_client_ses; @@ -469,7 +469,7 @@ static void freeSession(MXS_ROUTER* router_instance, void* router_client_ses) * @param router_session The session being closed */ static void -closeSession(MXS_ROUTER *instance, void *router_session) +closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) { ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *) router_session; DCB* backend_dcb; @@ -537,7 +537,7 @@ static void log_closed_session(mysql_server_cmd_t mysql_command, bool is_closed, * @return if succeed 1, otherwise 0 */ static int -routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *queue) +routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue) { ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *) router_session; @@ -656,7 +656,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) * @param queue The GWBUF with reply data */ static void -clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb) +clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb) { ss_dassert(backend_dcb->session->client_dcb != NULL); MXS_SESSION_ROUTE_REPLY(backend_dcb->session, queue); @@ -675,7 +675,7 @@ clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, DCB *backe * @param succp Result of action: true if router can continue * */ -static void handleError(MXS_ROUTER *instance, void *router_session, GWBUF *errbuf, +static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *errbuf, DCB *problem_dcb, mxs_error_action_t action, bool *succp) { diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 31fc797a8..63d53e391 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -69,14 +69,14 @@ */ static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static void *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *session); -static void freeSession(MXS_ROUTER *instance, void *session); -static int routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue); +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue); static void diagnostics(MXS_ROUTER *instance, DCB *dcb); -static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *queue, +static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, DCB *backend_dcb); -static void handleError(MXS_ROUTER *instance, void *router_session, +static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *errmsgbuf, DCB *backend_dcb, mxs_error_action_t action, bool *succp); static uint64_t getCapabilities(MXS_ROUTER* instance); @@ -250,7 +250,6 @@ static MXS_ROUTER *createInstance(SERVICE *service, char **options) return NULL; } router->service = service; - spinlock_init(&router->lock); /* * Until we know otherwise assume we have some available slaves. @@ -313,7 +312,7 @@ static MXS_ROUTER *createInstance(SERVICE *service, char **options) * @param session The MaxScale session (generic connection data) * @return Session specific data for this session, i.e. a router session */ -static void *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session) +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_inst; ROUTER_CLIENT_SES *client_rses = (ROUTER_CLIENT_SES *)MXS_CALLOC(1, sizeof(ROUTER_CLIENT_SES)); @@ -331,7 +330,6 @@ static void *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session) client_rses->client_dcb = session->client_dcb; client_rses->have_tmp_tables = false; client_rses->forced_node = NULL; - spinlock_init(&client_rses->rses_lock); memcpy(&client_rses->rses_config, &router->rwsplit_config, sizeof(client_rses->rses_config)); int router_nservers = router->service->n_dbref; @@ -404,12 +402,12 @@ static void *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session) * @param instance The router instance data * @param session The router session being closed */ -static void closeSession(MXS_ROUTER *instance, void *router_session) +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) { ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); - if (!router_cli_ses->rses_closed && rses_begin_locked_router_action(router_cli_ses)) + if (!router_cli_ses->rses_closed) { /** * Mark router session as closed. @c rses_closed is checked at the start @@ -465,8 +463,6 @@ static void closeSession(MXS_ROUTER *instance, void *router_session) } } } - - rses_end_locked_router_action(router_cli_ses); } } @@ -480,7 +476,7 @@ static void closeSession(MXS_ROUTER *instance, void *router_session) * @param router_client_session Client session * */ -static void freeSession(MXS_ROUTER *router_instance, void *router_client_session) +static void freeSession(MXS_ROUTER *router_instance, MXS_ROUTER_SESSION *router_client_session) { ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; @@ -553,7 +549,7 @@ void close_failed_bref(backend_ref_t *bref, bool fatal) * @param querybuf Buffer containing the query * @return 1 on success, 0 on error */ -static int routeQuery(MXS_ROUTER *instance, void *router_session, GWBUF *querybuf) +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *querybuf) { ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *) router_session; @@ -665,34 +661,27 @@ static void diagnostics(MXS_ROUTER *instance, DCB *dcb) * @param backend_dcb The backend DCB * @param queue The GWBUF with reply data */ -static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *writebuf, +static void clientReply(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *writebuf, DCB *backend_dcb) { - DCB *client_dcb; - ROUTER_INSTANCE *router_inst; - ROUTER_CLIENT_SES *router_cli_ses; - sescmd_cursor_t *scur = NULL; - backend_ref_t *bref; + ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)instance; + DCB *client_dcb = backend_dcb->session->client_dcb; - router_cli_ses = (ROUTER_CLIENT_SES *)router_session; - router_inst = (ROUTER_INSTANCE *)instance; CHK_CLIENT_RSES(router_cli_ses); /** * Lock router client session for secure read of router session members. * Note that this could be done without lock by using version # */ - if (!rses_begin_locked_router_action(router_cli_ses)) + if (router_cli_ses->rses_closed) { gwbuf_free(writebuf); - goto lock_failed; + return; } - /** Holding lock ensures that router session remains open */ - ss_dassert(backend_dcb->session != NULL); - client_dcb = backend_dcb->session->client_dcb; - /** Unlock */ - rses_end_locked_router_action(router_cli_ses); /** * 1. Check if backend received reply to sescmd. * 2. Check sescmd's state whether OK_PACKET has been @@ -702,32 +691,10 @@ static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *write * 3. If reply for this sescmd is sent, lock property cursor * and */ - if (client_dcb == NULL) - { - gwbuf_free(writebuf); - /** Log that client was closed before reply */ - goto lock_failed; - } - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - /** Log to debug that router was closed */ - goto lock_failed; - } - bref = get_bref_from_dcb(router_cli_ses, backend_dcb); - -#if !defined(FOR_BUG548_FIX_ONLY) - /** This makes the issue becoming visible in poll.c */ - if (bref == NULL) - { - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - goto lock_failed; - } -#endif + backend_ref_t *bref = get_bref_from_dcb(router_cli_ses, backend_dcb); CHK_BACKEND_REF(bref); - scur = &bref->bref_sescmd_cur; + sescmd_cursor_t *scur = &bref->bref_sescmd_cur; /** Statement was successfully executed, free the stored statement */ session_clear_stmt(backend_dcb->session); @@ -788,15 +755,7 @@ static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *write /** Write reply to client DCB */ MXS_SESSION_ROUTE_REPLY(backend_dcb->session, writebuf); } - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - /** Log to debug that router was closed */ - goto lock_failed; - } /** There is one pending session command to be executed. */ if (sescmd_cursor_is_active(scur)) { @@ -847,11 +806,6 @@ static void clientReply(MXS_ROUTER *instance, void *router_session, GWBUF *write gwbuf_free(bref->bref_pending_cmd); bref->bref_pending_cmd = NULL; } - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - -lock_failed: - return; } @@ -876,64 +830,6 @@ static uint64_t getCapabilities(MXS_ROUTER* instance) * functions are not intended for use outside the read write split router. */ -/** - * @brief Acquires lock to router client session if it is not closed. - * - * Parameters: - * @param rses - in, use - * - * - * @return true if router session was not closed. If return value is true - * it means that router is locked, and must be unlocked later. False, if - * router was closed before lock was acquired. - * - */ -bool rses_begin_locked_router_action(ROUTER_CLIENT_SES *rses) -{ - bool succp = false; - - if (rses == NULL) - { - return false; - } - - CHK_CLIENT_RSES(rses); - - if (rses->rses_closed) - { - - goto return_succp; - } - spinlock_acquire(&rses->rses_lock); - if (rses->rses_closed) - { - spinlock_release(&rses->rses_lock); - goto return_succp; - } - succp = true; - -return_succp: - return succp; -} - -/** to be inline'd */ - -/** - * @brief Releases router client session lock. - * - * Parameters: - * @param rses - - * - * - * @return void - * - */ -void rses_end_locked_router_action(ROUTER_CLIENT_SES *rses) -{ - CHK_CLIENT_RSES(rses); - spinlock_release(&rses->rses_lock); -} - /* * @brief Clear one or more bits in the backend reference state * @@ -1135,28 +1031,21 @@ int rses_get_max_replication_lag(ROUTER_CLIENT_SES *rses) */ backend_ref_t *get_bref_from_dcb(ROUTER_CLIENT_SES *rses, DCB *dcb) { - backend_ref_t *bref; - int i = 0; + ss_dassert(dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER); CHK_DCB(dcb); CHK_CLIENT_RSES(rses); - bref = rses->rses_backend_ref; - - while (i < rses->rses_nbackends) + for (int i = 0; i < rses->rses_nbackends; i++) { - if (bref->bref_dcb == dcb) + if (rses->rses_backend_ref[i].bref_dcb == dcb) { - break; + return &rses->rses_backend_ref[i]; } - bref++; - i += 1; } - if (i == rses->rses_nbackends) - { - bref = NULL; - } - return bref; + /** We should always have a valid backend reference */ + ss_dassert(false); + return NULL; } /** @@ -1284,17 +1173,19 @@ static bool rwsplit_process_router_options(ROUTER_INSTANCE *router, * Even if succp == true connecting to new slave may have failed. succp is to * tell whether router has enough master/slave connections to continue work. */ -static void handleError(MXS_ROUTER *instance, void *router_session, - GWBUF *errmsgbuf, DCB *problem_dcb, - mxs_error_action_t action, bool *succp) +static void handleError(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *errmsgbuf, + DCB *problem_dcb, + mxs_error_action_t action, + bool *succp) { - MXS_SESSION *session; ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *)router_session; - + CHK_CLIENT_RSES(rses); CHK_DCB(problem_dcb); - if (!rses_begin_locked_router_action(rses)) + if (rses->rses_closed) { /** Session is already closed */ problem_dcb->dcb_errhandle_called = true; @@ -1311,32 +1202,24 @@ static void handleError(MXS_ROUTER *instance, void *router_session, * be safe with the code as it stands on 9 Sept 2015 - MNB */ *succp = true; - rses_end_locked_router_action(rses); return; } else { problem_dcb->dcb_errhandle_called = true; } - session = problem_dcb->session; - backend_ref_t *bref = get_bref_from_dcb(rses, problem_dcb); + MXS_SESSION *session = problem_dcb->session; + ss_dassert(session); - if (session == NULL) - { - MXS_ERROR("Session of DCB %p is NULL, won't close the DCB.", problem_dcb); - ss_dassert(false); - *succp = false; - } - else if (DCB_ROLE_CLIENT_HANDLER == problem_dcb->dcb_role) + if (problem_dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER) { dcb_close(problem_dcb); *succp = false; } else { - CHK_SESSION(session); - CHK_CLIENT_RSES(rses); + backend_ref_t *bref = get_bref_from_dcb(rses, problem_dcb); switch (action) { @@ -1449,8 +1332,6 @@ static void handleError(MXS_ROUTER *instance, void *router_session, break; } } - - rses_end_locked_router_action(rses); } /** @@ -1570,7 +1451,6 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, bool succp; myrses = *rses; - ss_dassert(SPINLOCK_IS_LOCKED(&myrses->rses_lock)); ses = backend_dcb->session; CHK_SESSION(ses); diff --git a/server/modules/routing/readwritesplit/readwritesplit.h b/server/modules/routing/readwritesplit/readwritesplit.h index 679984d93..d687d0e4b 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.h +++ b/server/modules/routing/readwritesplit/readwritesplit.h @@ -307,7 +307,6 @@ struct router_client_session #if defined(SS_DEBUG) skygw_chk_t rses_chk_top; #endif - SPINLOCK rses_lock; /*< protects rses_deleted */ bool rses_closed; /*< true when closeSession is called */ rses_property_t* rses_properties[RSES_PROP_TYPE_COUNT]; /*< Properties listed by their type */ backend_ref_t* rses_master_ref; @@ -349,7 +348,6 @@ typedef struct typedef struct router_instance { SERVICE* service; /*< Pointer to service */ - SPINLOCK lock; /*< Lock for the instance data */ rwsplit_config_t rwsplit_config; /*< expanded config info from SERVICE */ int rwsplit_version; /*< version number for router's config */ ROUTER_STATS stats; /*< Statistics for this router */ diff --git a/server/modules/routing/readwritesplit/rwsplit_internal.h b/server/modules/routing/readwritesplit/rwsplit_internal.h index 0fdcaeb86..78f415218 100644 --- a/server/modules/routing/readwritesplit/rwsplit_internal.h +++ b/server/modules/routing/readwritesplit/rwsplit_internal.h @@ -56,7 +56,6 @@ bool handle_target_is_all(route_target_t route_target, GWBUF *querybuf, int packet_type, qc_query_type_t qtype); int determine_packet_type(GWBUF *querybuf, bool *non_empty_packet); void log_transaction_status(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, qc_query_type_t qtype); -void session_lock_failure_handling(GWBUF *querybuf, int packet_type, qc_query_type_t qtype); bool is_packet_a_one_way_message(int packet_type); sescmd_cursor_t *backend_ref_get_sescmd_cursor(backend_ref_t *bref); bool is_packet_a_query(int packet_type); @@ -65,8 +64,6 @@ bool send_readonly_error(DCB *dcb); /* * The following are implemented in readwritesplit.c */ -bool rses_begin_locked_router_action(ROUTER_CLIENT_SES *rses); -void rses_end_locked_router_action(ROUTER_CLIENT_SES *rses); void bref_clear_state(backend_ref_t *bref, bref_state_t state); void bref_set_state(backend_ref_t *bref, bref_state_t state); int router_handle_state_switch(DCB *dcb, DCB_REASON reason, void *data); diff --git a/server/modules/routing/readwritesplit/rwsplit_mysql.c b/server/modules/routing/readwritesplit/rwsplit_mysql.c index 6c4c8d56d..ab767e250 100644 --- a/server/modules/routing/readwritesplit/rwsplit_mysql.c +++ b/server/modules/routing/readwritesplit/rwsplit_mysql.c @@ -287,32 +287,6 @@ handle_target_is_all(route_target_t route_target, return result; } -/* This is MySQL specific */ -/** - * @brief Write an error message to the log indicating failure - * - * Used when an attempt to lock the router session fails. - * - * @param querybuf Query buffer containing packet - * @param packet_type Integer (enum) indicating type of packet - * @param qtype Query type - */ -void -session_lock_failure_handling(GWBUF *querybuf, int packet_type, qc_query_type_t qtype) -{ - if (packet_type != MYSQL_COM_QUIT) - { - /* NOTE: modutil_get_query is MySQL specific */ - char *query_str = modutil_get_query(querybuf); - - MXS_ERROR("Can't route %s:%s:\"%s\" to " - "backend server. Router is closed.", - STRPACKETTYPE(packet_type), STRQTYPE(qtype), - (query_str == NULL ? "(empty)" : query_str)); - MXS_FREE(query_str); - } -} - /* * Probably MySQL specific because of modutil function */ diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c b/server/modules/routing/readwritesplit/rwsplit_route_stmt.c index ae3f8a24c..183709d4f 100644 --- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c +++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.c @@ -70,16 +70,10 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, /* packet_type is a problem as it is MySQL specific */ packet_type = determine_packet_type(querybuf, &non_empty_packet); qtype = determine_query_type(querybuf, packet_type, non_empty_packet); + if (non_empty_packet) { - /** This might not be absolutely necessary as some parts of the code - * can only be executed by one thread at a time. */ - if (!rses_begin_locked_router_action(rses)) - { - return false; - } handle_multi_temp_and_load(rses, querybuf, packet_type, (int *)&qtype); - rses_end_locked_router_action(rses); if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { @@ -116,7 +110,7 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, { succp = handle_target_is_all(route_target, inst, rses, querybuf, packet_type, qtype); } - else if (rses_begin_locked_router_action(rses)) + else { /* Now we have a lock on the router session */ bool store_stmt = false; @@ -152,12 +146,6 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, ss_dassert(!store_stmt || TARGET_IS_SLAVE(route_target)); handle_got_target(inst, rses, querybuf, target_dcb, store_stmt); } - rses_end_locked_router_action(rses); - } - else - { - session_lock_failure_handling(querybuf, packet_type, qtype); - succp = false; } return succp; @@ -214,12 +202,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, { int rc; - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - goto return_succp; - } - for (i = 0; i < router_cli_ses->rses_nbackends; i++) { DCB *dcb = backend_ref[i].bref_dcb; @@ -244,15 +226,9 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, } } } - rses_end_locked_router_action(router_cli_ses); gwbuf_free(querybuf); goto return_succp; } - /** Lock router session */ - if (!rses_begin_locked_router_action(router_cli_ses)) - { - goto return_succp; - } if (router_cli_ses->rses_nbackends <= 0) { @@ -318,7 +294,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, if ((prop = rses_property_init(RSES_PROP_TYPE_SESCMD)) == NULL) { MXS_ERROR("Router session property initialization failed"); - rses_end_locked_router_action(router_cli_ses); return false; } @@ -328,7 +303,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, if (rses_property_add(router_cli_ses, prop) != 0) { MXS_ERROR("Session property addition failed."); - rses_end_locked_router_action(router_cli_ses); return false; } @@ -387,9 +361,6 @@ bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, atomic_add(&router_cli_ses->rses_nsescmd, 1); - /** Unlock router session */ - rses_end_locked_router_action(router_cli_ses); - return_succp: /** * Routing must succeed to all backends that are used. @@ -1364,7 +1335,6 @@ int rses_property_add(ROUTER_CLIENT_SES *rses, rses_property_t *prop) CHK_CLIENT_RSES(rses); CHK_RSES_PROP(prop); - ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock)); prop->rses_prop_rsession = rses; p = rses->rses_properties[prop->rses_prop_type]; diff --git a/server/modules/routing/readwritesplit/rwsplit_session_cmd.c b/server/modules/routing/readwritesplit/rwsplit_session_cmd.c index 4100dc1b8..72b474073 100644 --- a/server/modules/routing/readwritesplit/rwsplit_session_cmd.c +++ b/server/modules/routing/readwritesplit/rwsplit_session_cmd.c @@ -60,8 +60,6 @@ mysql_sescmd_t *rses_property_get_sescmd(rses_property_t *prop) } CHK_RSES_PROP(prop); - ss_dassert(prop->rses_prop_rsession == NULL || - SPINLOCK_IS_LOCKED(&prop->rses_prop_rsession->rses_lock)); sescmd = &prop->rses_prop_data.sescmd; @@ -134,14 +132,9 @@ GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf, backend_ref_t *bref, bool *reconnect) { - mysql_sescmd_t *scmd; - sescmd_cursor_t *scur; - ROUTER_CLIENT_SES *ses; - - scur = &bref->bref_sescmd_cur; - ss_dassert(SPINLOCK_IS_LOCKED(&(scur->scmd_cur_rses->rses_lock))); - scmd = sescmd_cursor_get_command(scur); - ses = (*scur->scmd_cur_ptr_property)->rses_prop_rsession; + sescmd_cursor_t *scur = &bref->bref_sescmd_cur; + mysql_sescmd_t *scmd = sescmd_cursor_get_command(scur); + ROUTER_CLIENT_SES *ses = (*scur->scmd_cur_ptr_property)->rses_prop_rsession; CHK_GWBUF(replybuf); /** @@ -273,7 +266,6 @@ mysql_sescmd_t *sescmd_cursor_get_command(sescmd_cursor_t *scur) { mysql_sescmd_t *scmd; - ss_dassert(SPINLOCK_IS_LOCKED(&(scur->scmd_cur_rses->rses_lock))); scur->scmd_cur_cmd = rses_property_get_sescmd(*scur->scmd_cur_ptr_property); CHK_MYSQL_SESCMD(scur->scmd_cur_cmd); @@ -293,7 +285,6 @@ bool sescmd_cursor_is_active(sescmd_cursor_t *sescmd_cursor) MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); return false; } - ss_dassert(SPINLOCK_IS_LOCKED(&sescmd_cursor->scmd_cur_rses->rses_lock)); succp = sescmd_cursor->scmd_cur_active; return succp; @@ -303,7 +294,6 @@ bool sescmd_cursor_is_active(sescmd_cursor_t *sescmd_cursor) void sescmd_cursor_set_active(sescmd_cursor_t *sescmd_cursor, bool value) { - ss_dassert(SPINLOCK_IS_LOCKED(&sescmd_cursor->scmd_cur_rses->rses_lock)); /** avoid calling unnecessarily */ ss_dassert(sescmd_cursor->scmd_cur_active != value); sescmd_cursor->scmd_cur_active = value; @@ -420,8 +410,6 @@ static bool sescmd_cursor_next(sescmd_cursor_t *scur) ss_dassert(scur != NULL); ss_dassert(*(scur->scmd_cur_ptr_property) != NULL); - ss_dassert(SPINLOCK_IS_LOCKED( - &(*(scur->scmd_cur_ptr_property))->rses_prop_rsession->rses_lock)); /** Illegal situation */ if (scur == NULL || *scur->scmd_cur_ptr_property == NULL || diff --git a/server/modules/routing/schemarouter/schemarouter.c b/server/modules/routing/schemarouter/schemarouter.c index 243c15128..1256fb9b9 100644 --- a/server/modules/routing/schemarouter/schemarouter.c +++ b/server/modules/routing/schemarouter/schemarouter.c @@ -58,23 +58,23 @@ */ static MXS_ROUTER* createInstance(SERVICE *service, char **options); -static void* newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, void *session); -static void freeSession(MXS_ROUTER *instance, void *session); -static int routeQuery(MXS_ROUTER *instance, void *session, GWBUF *queue); -static void diagnostic(MXS_ROUTER *instance, DCB *dcb); +static MXS_ROUTER_SESSION* newSession(MXS_ROUTER *instance, MXS_SESSION *session); +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); +static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue); +static void diagnostic(MXS_ROUTER *instance, DCB *dcb); static void clientReply(MXS_ROUTER* instance, - void* router_session, - GWBUF* queue, - DCB* backend_dcb); + MXS_ROUTER_SESSION* router_session, + GWBUF* queue, + DCB* backend_dcb); -static void handleError(MXS_ROUTER* instance, - void* router_session, - GWBUF* errmsgbuf, - DCB* backend_dcb, +static void handleError(MXS_ROUTER* instance, + MXS_ROUTER_SESSION* router_session, + GWBUF* errmsgbuf, + DCB* backend_dcb, mxs_error_action_t action, - bool* succp); + bool* succp); static backend_ref_t* get_bref_from_dcb(ROUTER_CLIENT_SES* rses, DCB* dcb); static route_target_t get_shard_route_target(qc_query_type_t qtype, @@ -847,7 +847,7 @@ enum shard_map_state shard_map_update_state(shard_map_t *self, ROUTER_INSTANCE* * @param session The session itself * @return Session specific data for this session */ -static void* newSession(MXS_ROUTER* router_inst, MXS_SESSION* session) +static MXS_ROUTER_SESSION* newSession(MXS_ROUTER* router_inst, MXS_SESSION* session) { backend_ref_t* backend_ref; /*< array of backend references (DCB, BACKEND, cursor) */ ROUTER_CLIENT_SES* client_rses = NULL; @@ -1040,7 +1040,7 @@ static void* newSession(MXS_ROUTER* router_inst, MXS_SESSION* session) * @param instance The router instance data * @param session The session being closed */ -static void closeSession(MXS_ROUTER* instance, void* router_session) +static void closeSession(MXS_ROUTER* instance, MXS_ROUTER_SESSION* router_session) { ROUTER_CLIENT_SES* router_cli_ses; ROUTER_INSTANCE* inst; @@ -1135,7 +1135,7 @@ static void closeSession(MXS_ROUTER* instance, void* router_session) } } -static void freeSession(MXS_ROUTER* router_instance, void* router_client_session) +static void freeSession(MXS_ROUTER* router_instance, MXS_ROUTER_SESSION* router_client_session) { ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; @@ -1612,7 +1612,7 @@ bool send_database_list(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client) * */ static int routeQuery(MXS_ROUTER* instance, - void* router_session, + MXS_ROUTER_SESSION* router_session, GWBUF* qbuf) { qc_query_type_t qtype = QUERY_TYPE_UNKNOWN; @@ -2238,7 +2238,7 @@ static void diagnostic(MXS_ROUTER *instance, DCB *dcb) * @param queue The GWBUF with reply data */ static void clientReply(MXS_ROUTER* instance, - void* router_session, + MXS_ROUTER_SESSION* router_session, GWBUF* buffer, DCB* backend_dcb) { @@ -3550,12 +3550,12 @@ return_succp: * Even if succp == true connecting to new slave may have failed. succp is to * tell whether router has enough master/slave connections to continue work. */ -static void handleError(MXS_ROUTER* instance, - void* router_session, - GWBUF* errmsgbuf, - DCB* problem_dcb, +static void handleError(MXS_ROUTER* instance, + MXS_ROUTER_SESSION* router_session, + GWBUF* errmsgbuf, + DCB* problem_dcb, mxs_error_action_t action, - bool* succp) + bool* succp) { MXS_SESSION* session; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; diff --git a/server/modules/routing/testroute/CMakeLists.txt b/server/modules/routing/testroute/CMakeLists.txt deleted file mode 100644 index 58c91cfd2..000000000 --- a/server/modules/routing/testroute/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_library(testroute SHARED testroute.c) -target_link_libraries(testroute maxscale-common) -set_target_properties(testroute PROPERTIES VERSION "1.0.0") -install_module(testroute core)