Usare la libreria client non-bloccante

Stai visualizzando una vecchia versione di questo article. Visualizza la versione più recente.

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.

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.

Commenti

Sto caricando i commenti......
Content reproduced on this site is the property of its respective owners, and this content is not reviewed in advance by MariaDB. The views, information and opinions expressed by this content do not necessarily represent those of MariaDB or any other party.