mirror of
https://github.com/MariaDB/server.git
synced 2025-09-02 09:41:40 +03:00
MWL#192: Non-blocking client API for libmysqlclient.
All client functions that can block on I/O have alternate _start() and _cont() versions that do not block but return control back to the application, which can then issue I/O wait in its own fashion and later call back into the library to continue the operation. Works behind the scenes by spawning a co-routine/fiber to run the blocking operation and suspend it while waiting for I/O. This co-routine/fiber use is invisible to applications. For i368/x86_64 on GCC, uses very fast assembler co-routine support. On Windows uses native Win32 Fibers. Falls back to POSIX ucontext on other platforms. Assembler routines for more platforms are relatively easy to add by extending mysys/my_context.c, eg. similar to the Lua lcoco library. For testing, mysqltest and mysql_client_test are extended with the option --non-blocking-api. This causes the programs to use the non-blocking API for database access. mysql-test-run.pl has a similar option --non-blocking-api that uses this, as well as additional testcases. An example program tests/async_queries.c is included that uses the new non-blocking API with libevent to show how, in a single-threaded program, to issue many queries in parallel against a database. client/async_example.c: Fix const warning ****** Fix bug with wrong timeout value for poll(). include/Makefile.am: Fix missing include for `make dist` include/mysql.h: Add prototypes for all non-blocking API calls. include/mysql.h.pp: Add prototypes for all non-blocking API calls. mysys/my_context.c: Fix type warning for makecontext() function pointer argument. sql-common/mysql_async.c: Fix crashes in the non-blocking API for functions that can take MYSQL argument that is NULL. tests/Makefile.am: Add header file to `make dist` tests/mysql_client_test.c: Replace blocking calls with wrappers around the non-blocking calls, used in mysql_client_test to test the new non-blocking API. tests/nonblock-wrappers.h: Replace blocking calls with wrappers around the non-blocking calls, used in mysql_client_test to test the new non-blocking API.
This commit is contained in:
222
include/my_context.h
Normal file
222
include/my_context.h
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
Copyright 2011 Kristian Nielsen
|
||||
|
||||
Experiments with non-blocking libmysql.
|
||||
|
||||
This is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
Simple API for spawning a co-routine, to be used for async libmysqlclient.
|
||||
|
||||
Idea is that by implementing this interface using whatever facilities are
|
||||
available for given platform, we can use the same code for the generic
|
||||
libmysqlclient-async code.
|
||||
|
||||
(This particular implementation uses Posix ucontext swapcontext().)
|
||||
*/
|
||||
|
||||
#ifdef __WIN__
|
||||
#define MY_CONTEXT_USE_WIN32_FIBERS 1
|
||||
#elif defined(__GNUC__) && __GNUC__ >= 3 && defined(__x86_64__)
|
||||
#define MY_CONTEXT_USE_X86_64_GCC_ASM
|
||||
#elif defined(__GNUC__) && __GNUC__ >= 3 && defined(__i386__)
|
||||
#define MY_CONTEXT_USE_I386_GCC_ASM
|
||||
#else
|
||||
#define MY_CONTEXT_USE_UCONTEXT
|
||||
#endif
|
||||
|
||||
#ifdef MY_CONTEXT_USE_WIN32_FIBERS
|
||||
struct my_context {
|
||||
void (*user_func)(void *);
|
||||
void *user_arg;
|
||||
void *app_fiber;
|
||||
void *lib_fiber;
|
||||
int return_value;
|
||||
#ifndef DBUG_OFF
|
||||
void *dbug_state;
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef MY_CONTEXT_USE_UCONTEXT
|
||||
#include <ucontext.h>
|
||||
|
||||
struct my_context {
|
||||
void (*user_func)(void *);
|
||||
void *user_data;
|
||||
void *stack;
|
||||
size_t stack_size;
|
||||
ucontext_t base_context;
|
||||
ucontext_t spawned_context;
|
||||
int active;
|
||||
#ifdef HAVE_VALGRIND_VALGRIND_H
|
||||
unsigned int valgrind_stack_id;
|
||||
#endif
|
||||
#ifndef DBUG_OFF
|
||||
void *dbug_state;
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef MY_CONTEXT_USE_X86_64_GCC_ASM
|
||||
#include <stdint.h>
|
||||
|
||||
struct my_context {
|
||||
uint64_t save[9];
|
||||
void *stack_top;
|
||||
void *stack_bot;
|
||||
#ifdef HAVE_VALGRIND_VALGRIND_H
|
||||
unsigned int valgrind_stack_id;
|
||||
#endif
|
||||
#ifndef DBUG_OFF
|
||||
void *dbug_state;
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef MY_CONTEXT_USE_I386_GCC_ASM
|
||||
#include <stdint.h>
|
||||
|
||||
struct my_context {
|
||||
uint64_t save[7];
|
||||
void *stack_top;
|
||||
void *stack_bot;
|
||||
#ifdef HAVE_VALGRIND_VALGRIND_H
|
||||
unsigned int valgrind_stack_id;
|
||||
#endif
|
||||
#ifndef DBUG_OFF
|
||||
void *dbug_state;
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
Initialize an asynchroneous context object.
|
||||
Returns 0 on success, non-zero on failure.
|
||||
*/
|
||||
extern int my_context_init(struct my_context *c, size_t stack_size);
|
||||
|
||||
/* Free an asynchroneous context object, deallocating any resources used. */
|
||||
extern void my_context_destroy(struct my_context *c);
|
||||
|
||||
/*
|
||||
Spawn an asynchroneous context. The context will run the supplied user
|
||||
function, passing the supplied user data pointer.
|
||||
|
||||
The context must have been initialised with my_context_init() prior to
|
||||
this call.
|
||||
|
||||
The user function may call my_context_yield(), which will cause this
|
||||
function to return 1. Then later my_context_continue() may be called, which
|
||||
will resume the asynchroneous context by returning from the previous
|
||||
my_context_yield() call.
|
||||
|
||||
When the user function returns, this function returns 0.
|
||||
|
||||
In case of error, -1 is returned.
|
||||
*/
|
||||
extern int my_context_spawn(struct my_context *c, void (*f)(void *), void *d);
|
||||
|
||||
/*
|
||||
Suspend an asynchroneous context started with my_context_spawn.
|
||||
|
||||
When my_context_yield() is called, execution immediately returns from the
|
||||
last my_context_spawn() or my_context_continue() call. Then when later
|
||||
my_context_continue() is called, execution resumes by returning from this
|
||||
my_context_yield() call.
|
||||
|
||||
Returns 0 if ok, -1 in case of error.
|
||||
*/
|
||||
extern int my_context_yield(struct my_context *c);
|
||||
|
||||
/*
|
||||
Resume an asynchroneous context. The context was spawned by
|
||||
my_context_spawn(), and later suspended inside my_context_yield().
|
||||
|
||||
The asynchroneous context may be repeatedly suspended with
|
||||
my_context_yield() and resumed with my_context_continue().
|
||||
|
||||
Each time it is suspended, this function returns 1. When the originally
|
||||
spawned user function returns, this function returns 0.
|
||||
|
||||
In case of error, -1 is returned.
|
||||
*/
|
||||
extern int my_context_continue(struct my_context *c);
|
||||
|
||||
|
||||
struct mysql_async_context {
|
||||
/*
|
||||
This is set to the value that should be returned from foo_start() or
|
||||
foo_cont() when a call is suspended.
|
||||
It is also set to the event(s) that triggered when a suspended call is
|
||||
resumed, eg. whether we woke up due to connection completed or timeout
|
||||
in mysql_real_connect_cont().
|
||||
*/
|
||||
unsigned int ret_status;
|
||||
/*
|
||||
This is set to the result of the whole asynchronous operation when it
|
||||
completes. It uses a union, as different calls have different return
|
||||
types.
|
||||
*/
|
||||
union {
|
||||
void *r_ptr;
|
||||
const void *r_const_ptr;
|
||||
int r_int;
|
||||
my_bool r_my_bool;
|
||||
} ret_result;
|
||||
/*
|
||||
The timeout value, for suspended calls that need to wake up on a timeout
|
||||
(eg. mysql_real_connect_start().
|
||||
*/
|
||||
unsigned int timeout_value;
|
||||
/*
|
||||
This flag is set when we are executing inside some asynchronous call
|
||||
foo_start() or foo_cont(). It is used to decide whether to use the
|
||||
synchronous or asynchronous version of calls that may block such as
|
||||
recv().
|
||||
|
||||
Note that this flag is not set when a call is suspended, eg. after
|
||||
returning from foo_start() and before re-entering foo_cont().
|
||||
*/
|
||||
my_bool active;
|
||||
/*
|
||||
This flag is set when an asynchronous operation is in progress, but
|
||||
suspended. Ie. it is set when foo_start() or foo_cont() returns because
|
||||
the operation needs to block, suspending the operation.
|
||||
|
||||
It is used to give an error (rather than crash) if the application
|
||||
attempts to call some foo_cont() method when no suspended operation foo is
|
||||
in progress.
|
||||
*/
|
||||
my_bool suspended;
|
||||
/*
|
||||
If non-NULL, this is a pointer to a callback hook that will be invoked with
|
||||
the user data argument just before the context is suspended, and just after
|
||||
it is resumed.
|
||||
*/
|
||||
void (*suspend_resume_hook)(my_bool suspend, void *user_data);
|
||||
void *suspend_resume_hook_user_data;
|
||||
/*
|
||||
This is used to save the execution contexts so that we can suspend an
|
||||
operation and switch back to the application context, to resume the
|
||||
suspended context later when the application re-invokes us with
|
||||
foo_cont().
|
||||
*/
|
||||
struct my_context async_context;
|
||||
};
|
Reference in New Issue
Block a user