mirror of
https://github.com/MariaDB/server.git
synced 2025-04-18 21:44:20 +03:00
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.
208 lines
4.7 KiB
C
208 lines
4.7 KiB
C
/*
|
|
Copyright 2011 Kristian Nielsen and Monty Program Ab.
|
|
|
|
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/>.
|
|
*/
|
|
|
|
|
|
#ifndef __WIN__
|
|
#include <poll.h>
|
|
#else
|
|
#include <WinSock2.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <mysql.h>
|
|
|
|
#define SL(s) (s), sizeof(s)
|
|
|
|
static const char *my_groups[]= { "client", NULL };
|
|
|
|
static int
|
|
wait_for_mysql(MYSQL *mysql, int status)
|
|
{
|
|
#ifdef __WIN__
|
|
fd_set rs, ws, es;
|
|
int res;
|
|
struct timeval tv, *timeout;
|
|
my_socket s= mysql_get_socket(mysql);
|
|
FD_ZERO(&rs);
|
|
FD_ZERO(&ws);
|
|
FD_ZERO(&es);
|
|
if (status & MYSQL_WAIT_READ)
|
|
FD_SET(s, &rs);
|
|
if (status & MYSQL_WAIT_WRITE)
|
|
FD_SET(s, &ws);
|
|
if (status & MYSQL_WAIT_EXCEPT)
|
|
FD_SET(s, &es);
|
|
if (status & MYSQL_WAIT_TIMEOUT)
|
|
{
|
|
tv.tv_sec= mysql_get_timeout_value(mysql);
|
|
tv.tv_usec= 0;
|
|
timeout= &tv;
|
|
}
|
|
else
|
|
timeout= NULL;
|
|
res= select(1, &rs, &ws, &es, timeout);
|
|
if (res == 0)
|
|
return MYSQL_WAIT_TIMEOUT;
|
|
else if (res == SOCKET_ERROR)
|
|
{
|
|
/*
|
|
In a real event framework, we should handle errors and re-try the select.
|
|
*/
|
|
return MYSQL_WAIT_TIMEOUT;
|
|
}
|
|
else
|
|
{
|
|
int status= 0;
|
|
if (FD_ISSET(s, &rs))
|
|
status|= MYSQL_WAIT_READ;
|
|
if (FD_ISSET(s, &ws))
|
|
status|= MYSQL_WAIT_WRITE;
|
|
if (FD_ISSET(s, &es))
|
|
status|= MYSQL_WAIT_EXCEPT;
|
|
return status;
|
|
}
|
|
#else
|
|
struct pollfd pfd;
|
|
int timeout;
|
|
int res;
|
|
|
|
pfd.fd= mysql_get_socket(mysql);
|
|
pfd.events=
|
|
(status & MYSQL_WAIT_READ ? POLLIN : 0) |
|
|
(status & MYSQL_WAIT_WRITE ? POLLOUT : 0) |
|
|
(status & MYSQL_WAIT_EXCEPT ? POLLPRI : 0);
|
|
if (status & MYSQL_WAIT_TIMEOUT)
|
|
timeout= 1000*mysql_get_timeout_value(mysql);
|
|
else
|
|
timeout= -1;
|
|
res= poll(&pfd, 1, timeout);
|
|
if (res == 0)
|
|
return MYSQL_WAIT_TIMEOUT;
|
|
else if (res < 0)
|
|
{
|
|
/*
|
|
In a real event framework, we should handle EINTR and re-try the poll.
|
|
*/
|
|
return MYSQL_WAIT_TIMEOUT;
|
|
}
|
|
else
|
|
{
|
|
int status= 0;
|
|
if (pfd.revents & POLLIN)
|
|
status|= MYSQL_WAIT_READ;
|
|
if (pfd.revents & POLLOUT)
|
|
status|= MYSQL_WAIT_WRITE;
|
|
if (pfd.revents & POLLPRI)
|
|
status|= MYSQL_WAIT_EXCEPT;
|
|
return status;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
fatal(MYSQL *mysql, const char *msg)
|
|
{
|
|
fprintf(stderr, "%s: %s\n", msg, mysql_error(mysql));
|
|
exit(1);
|
|
}
|
|
|
|
static void
|
|
doit(const char *host, const char *user, const char *password)
|
|
{
|
|
int err;
|
|
MYSQL mysql, *ret;
|
|
MYSQL_RES *res;
|
|
MYSQL_ROW row;
|
|
int status;
|
|
|
|
mysql_init(&mysql);
|
|
mysql_options(&mysql, MYSQL_READ_DEFAULT_GROUP, "myapp");
|
|
|
|
/* Returns 0 when done, else flag for what to wait for when need to block. */
|
|
status= mysql_real_connect_start(&ret, &mysql, host, user, password, NULL,
|
|
0, NULL, 0);
|
|
while (status)
|
|
{
|
|
status= wait_for_mysql(&mysql, status);
|
|
status= mysql_real_connect_cont(&ret, &mysql, status);
|
|
}
|
|
|
|
if (!ret)
|
|
fatal(&mysql, "Failed to mysql_real_connect()");
|
|
|
|
status= mysql_real_query_start(&err, &mysql, SL("SHOW STATUS"));
|
|
while (status)
|
|
{
|
|
status= wait_for_mysql(&mysql, status);
|
|
status= mysql_real_query_cont(&err, &mysql, status);
|
|
}
|
|
if (err)
|
|
fatal(&mysql, "mysql_real_query() returns error");
|
|
|
|
/* This method cannot block. */
|
|
res= mysql_use_result(&mysql);
|
|
if (!res)
|
|
fatal(&mysql, "mysql_use_result() returns error");
|
|
|
|
for (;;)
|
|
{
|
|
status= mysql_fetch_row_start(&row, res);
|
|
while (status)
|
|
{
|
|
status= wait_for_mysql(&mysql, status);
|
|
status= mysql_fetch_row_cont(&row, res, status);
|
|
}
|
|
if (!row)
|
|
break;
|
|
printf("%s: %s\n", row[0], row[1]);
|
|
}
|
|
if (mysql_errno(&mysql))
|
|
fatal(&mysql, "Got error while retrieving rows");
|
|
mysql_free_result(res);
|
|
|
|
/* I suppose this must be non-blocking too. */
|
|
mysql_close(&mysql);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
if (argc != 4)
|
|
{
|
|
fprintf(stderr, "Usage: %s <host> <user> <password>\n", argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
err= mysql_library_init(argc, argv, (char **)my_groups);
|
|
if (err)
|
|
{
|
|
fprintf(stderr, "Fatal: mysql_library_init() returns error: %d\n", err);
|
|
exit(1);
|
|
}
|
|
|
|
doit(argv[1], argv[2], argv[3]);
|
|
|
|
mysql_library_end();
|
|
|
|
return 0;
|
|
}
|