Usare la libreria client non-bloccante
Note: This page describes features in the source repository for MariaDB 5.5. There are currently no official packages or binaries available for download which contain the features. If you want to try out any of the new features described here you will need to get and compile the code yourself.
Contents
L'API client non-bloccante di MariaDB è progettata allo stesso modo delle normali chiamate bloccanti, perché sia più semplice apprenderla e ricordarla. E' inoltre semplice tradurre il codice che utilizza l'API bloccante in modo che utilizzi quella non-bloccante (o vice versa). Infine è facile mischiare chiamate bloccanti e chiamate non bloccanti nelle stesse porzioni di codice.
Per ogni chiamata alla libreria che potrebbe bloccare i socket I/o, come 'int mysql_real_query(MYSQL, query, lunghezza_query)
', sono state introdotte due nuove funzioni non-bloccanti:
int mysql_real_query_start(&status, MYSQL, query, lunghezza_query) int mysql_real_query_start(&status, MYSQL, stato_di_attesa)
Per effettuare operazioni non bloccanti, una applicazione chiama prima mysql_real_query_start()
invece di mysql_real_query()
, passando gli stessi parametri.
Se mysql_real_query_start()
restituisce zero, allora l'operazione è stata completata senza blocchi, e 'status' è impostato al valore che normalmente verrebbe restituito da mysql_real_query()
.
Altrimenti, il valore restituito da mysql_real_query_start()
è una maschera di bit che indica gli eventi che la libreria sta aspettando. Può essere MYSQL_WAIT_READ
, MYSQL_WAIT_WRITE
o MYSQL_WAIT_EXCEPT
, che corrispondono ai flag simili di select()
e poll()
; può includere MYSQL_WAIT_TIMEOUT
quando attende il verificarsi di un timeout (ad esempio il timeout della connessione).
In questo caso, l'applicazione continua l'esecuzione ed eventualmente controlla che le condizioni appropriate si verifichino nel socket (o attende il timeout). Quando accade, l'applicazione può recuperare l'operazione chiamando mysql_real_query_cont()
, passando in 'wait_status' una maschera di bit che indica gli eventi effettivamente verificatisi.
Così come mysql_real_query_start()
, mysql_real_query_cont()
restituisce zero quando ha terminato, o una maschera di bit degli eventi che deve attendere. Quindi l'applicazione continua ripetutamente a chiamare mysql_real_query_cont()
, intervallato con altre elaborazioni a suo piacimento, finché non viene restituito zero; dopodiché il risultato dell'operazione verrà registrato in 'status'.
Alcune chiamate, come mysql_option()
, non utilizzano alcun socket I/O, pertanto non bloccano mai l'esecuzione. Per queste funzioni, non vi sono chiamate separate _start()
e _cont()
. Si veda la pagina "Riferimento all'API non-bloccante" per un elenco completo delle funzioni bloccanti e quelle non-bloccanti.
Il controllo degli eventi sui socket / timeout può essere effettuato con select()
, o poll()
, o un meccanismo simile. Però spesso viene fatto utilizzando un framework di livello più altro (come libevent), che fornisce meccanismi per registrare ed effettuare certe azioni al determinarsi di certe condizioni.
Il descrittore del socket che deve essere controllato quando si attendono gli eventi può essere ottenuto con mysql_get_socket()
. La durata di un qualsiasi timeout si può ottenere con mysql_get_timeout_value()
.
Ecco un esempio triviale (ma completo) su come eseguire una query con l'API non-bloccante. L'esempio è reperibile nei sorgenti di MariaDB, in client/async_example.c
. (Un altro esempio più esteso e realistico, che fa uso di libevent, si trova in tests/async_queries.c
):
static void run_query(const char *host, const char *user, const char *password) { int err, status; MYSQL mysql, *ret; MYSQL_RES *res; MYSQL_ROW row; mysql_init(&mysql); mysql_options(&mysql, MYSQL_OPT_NONBLOCK, 0); 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); mysql_close(&mysql); } /* Helper function to do the waiting for events on the socket. */ static int wait_for_mysql(MYSQL *mysql, int status) { struct pollfd pfd; int timeout, 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) 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; } }
Setting MYSQL_OPT_NONBLOCK
Before using any non-blocking operation, it is necessary to enable it first
by setting the MYSQL_OPT_NONBLOCK
option:
mysql_options(&mysql, MYSQL_OPT_NONBLOCK, 0);
This call can be made at any time — typically it will
be done at the start, before mysql_real_connect()
, but it can be done at
any time to start using non-blocking operations.
If a non-blocking operation is attempted without setting the
MYSQL_OPT_NONBLOCK
option, the program will typically crash with a NULL
pointer exception.
The argument for MYSQL_OPT_NONBLOCK
is the size of the stack used to save
the state of a non-blocking operation while it is waiting for I/O and the
application is doing other processing. Normally, applications will not have to
change this, and it can be passed as zero to use the default value.
Mixing blocking and non-blocking operation
It is possible to freely mix blocking and non-blocking calls on the same
MYSQL
connection.
Thus, an application can do a normal blocking mysql_real_connect()
and
subsequently do a non-blocking mysql_real_query_start()
. Or vice versa, do
a non-blocking mysql_real_connect_start()
, and later do a blocking
mysql_real_query()
on the resulting connection.
Mixing can be useful to allow code to use the simpler blocking API in parts of the program where waiting is not a problem. For example establishing the connection(s) at program startup, or doing small quick queries between large, long-running ones.
The only restriction is that any previous non-blocking operation must have finished before starting a new blocking (or non-blocking) operation, see the next section: "Terminating a non-blocking operation early" below.
Terminating a non-blocking operation early
When a non-blocking operation is started with mysql_real_query_start()
or
another _start()
function, it must be allowed to finish before starting a new
operation. Thus, the application must continue calling mysql_real_query_cont()
until zero is returned, indicating that the operation is completed. It is not
allowed to leave one operation "hanging" in the middle of processing and then
start a new one on top of it.
It is, however, permissible to terminate the connection completely with
mysql_close()
in the middle of processing a non-blocking call. A new
connection must then be initiated with mysql_real_connect
before new
queries can be run, either with a new MYSQL
object or re-using the old one.
In the future, we may implement an abort facility to force an on-going
operation to terminate as quickly as possible (but it will still be necessary
to call mysql_real_query_cont()
one last time after abort, allowing it to
clean up the operation and return immediately with an appropriate error code).
Restrictions
DNS
When mysql_real_connect_start()
is passed a hostname (as opposed to a local
unix socket or an IP address, it may need to look up the hostname in DNS,
depending on local host configuration (e.g. if the name is not in
/etc/hosts
or cached). Such DNS lookups do not happen in a non-blocking
way. This means that mysql_real_connect_start()
will not return control to
the application while waiting for the DNS response. Thus the application may
"hang" for some time if DNS is slow or non-functional.
If this is a problem, the application can pass an IP address to
mysql_real_connect_start()
instead of a hostname, which avoids the problem.
The IP address can be obtained by the application with whatever non-blocking
DNS loopup operation is available to it from the operating system or event
framework used. Alternatively, a simple solution may be to just add the
hostname to the local host lookup file (/etc/hosts
on Posix/Unix/Linux
machines).
Windows Named Pipes
There is no support in the non-blocking API for connections using Windows named pipes.
Named pipes can still be used, using either the blocking or the non-blocking API. However, operations that need to wait on I/O on the named pipe will not return control to the application; instead they will "hang" waiting for the operation to complete, just like the normal blocking API calls.