Usare la libreria client non-bloccante
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"); /* Questo metodo non può bloccare. */ 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); } /* Funzione ausiliare che attende gli eventi nel 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; } }
Impostare MYSQL_OPT_NONBLOCK
Prima di utilizzare le operazioni non-bloccanti, è necessario abilitare impostando l'opzione MYSQL_OPT_NONBLOCK
:
mysql_options(&mysql, MYSQL_OPT_NONBLOCK, 0);
Questa chiamata può essere effettuata in qualsiasi momento — tipicamente si trova all'inizio, prima di mysql_real_connect()
— per iniziare ad utilizzare le operazioni non-bloccanti.
Se si tenta di eseguire un'operazione non bloccante senza impostare l'opzione MYSQL_OPT_NONBLOCK
, normalmente il programma va in crash con un'eccezione di puntatore NULL
.
L'argomento di MYSQL_OPT_NONBLOCK
indica le dimensioni dellos tack utilizzato per salvare lo stato dell'operazione non-bloccante quando questa attende l'I/O e l'applicazione sta facendo altro. Normalmente, le applicazioni non hanno mai bisogno di modificare questo argomento, ed è possibile passare uno zero per impostarlo al valore predefinito.
Combinare operazioni bloccanti e non-bloccanti
E' possibile combinare azioni bloccanti e non-bloccanti sulla stessa connessione a MYSQL
.
Un'applicazione può perciò effettuare un normale mysql_real_connect()
bloccante e in seguito eseguire un mysql_real_query_start()
non-bloccante. Viceversa, può anche effettuare mysql_real_connect_start()
e più tardi mysql_real_query()
sulla connessione restituita.
Combinare queste operazioni può essere utile per scrivere codice che utilizzi la più semplice API bloccante laddove le attese non sono un problema. Per esempio può stabilire la connessione all'avvio del programma, o lanciare piccole query che vengono eseguite in mezzo ad altre, che richiedono molto più tempo.
L'unica restrizione è che, prima di iniziare una nuova operazione (bloccante o meno) tutte le precedenti operazioni non-bloccanti devono essere concluse. Si veda la prossima sezione sotto: "Terminare una operazione non-bloccante prima del tempo".
Terminare una operazione non-bloccante prima del tempo
Quando una operazione non-bloccante viene lanciata con mysql_real_query_start()
o qualche altra funzione _start()
, deve terminare prima che una nuova operazione possa iniziare. Per questo l'applicazione deve continuare a chiamare mysql_real_query_cont()
finché non viene restituito zero, che significa che l'operazione è stata completata. Non è permesso lanciare un'operazione "in attesa" a metà della sua esecuzione e avviarne una nuova.
E' tuttavia permesso terminare la connessione completamente con mysql_close()
anche mentre un'operazione non-bloccante è ancora in esecuzione. Una nuova connessione dovrà poi essere istanziata con mysql_real_connect
, prima che altre query possano essere lanciate, sia che si utilizzi un nuovo oggetto MYSQL
sia che si riusi quello vecchio.
In futuro, potrebbe essere implementato un nuovo meccanismo di abort, che permetta di terminare forzatamente un'operazione in corso appena possibile (ma sarà comunque necessario chiamara mysql_real_query_cont()
un'ultima volta dopo l'abort, perché ripulisca l'operazione e restituisca immediatamente il codice di errore opportuno).
Restrizioni
DNS
Quando un nome host viene passato a mysql_real_connect_start()
(invece di un socket Unix locale o un indirizzo IP), la libreria potrebbe aver bisogno di cercare il nome host in un DNS, a seconda della configurazione che si ha (per esempio se il nome non è in /etc/hosts
o nella cache). Queste ricerche DNS non sono in modalità non-bloccante. Questo significa che that mysql_real_connect_start()
non restituisce il controllo all'applicazione mentre attende la risposta dal DNS. Perciò l'applicazione potrebbe rimanere bloccata per un po', se il DNS è lento o non è in funzione.
Se questo è un problema, l'applicazione può passare un indirizzo IP anzichè un nome host a mysql_real_connect_start()
. L'indirizzo IP può essere ottenuto dall'applicazione con una qualsiasi operazione di ricerca DNS non bloccante che sia disponibile nel sistema operativo o nei framework utilizzati. In alternativa, una soluzione semplice potrebbe essere aggiungere il nome al file locale degli host (/etc/hosts
su macchine Posix/Unix/Linux).
Connessioni con le Named Pipes e la memoria condivisa di Windows
L'API non bloccante non supporta in alcun modo le connessioni attraverso le named pipes o la memoria condivisa di Windows.
E' comunque possibile usarle, sia con l'API bloccante sia con quella non-bloccante. Tuttavia, le operazioni che necessitano di attendere l'I/O su una named pipe non restituiscono il controllo all'applicazione, ma la tengono bloccata finché non saranno completate, esattamente come le normali chiamate all'API bloccante.