LGTM It is good to finally have timeouts that work in libiscsi, and a consumer that can use and benefit from it.
On Tue, Jun 16, 2015 at 4:45 AM, Peter Lieven <p...@kamp.de> wrote: > libiscsi starting with 1.15 will properly support timeout of iscsi > commands. The default will remain no timeout, but this can > be changed via cmdline parameters, e.g.: > > qemu -iscsi timeout=30 -drive file=iscsi://... > > If a timeout occurs a reconnect is scheduled and the timed out command > will be requeued for processing after a successful reconnect. > > The required API call iscsi_set_timeout is present since libiscsi > 1.10 which was released in October 2013. However, due to some bugs > in the libiscsi code the use is not recommended before version 1.15. > > Please note that this patch bumps the libiscsi requirement to 1.10 > to have all function and macros defined. The patch fixes also a > off-by-one error in the NOP timeout calculation which was fixed > while touching these code parts. > > Signed-off-by: Peter Lieven <p...@kamp.de> > --- > block/iscsi.c | 87 > ++++++++++++++++++++++++++++++++++++++++++--------------- > configure | 6 ++-- > qemu-options.hx | 4 +++ > 3 files changed, 72 insertions(+), 25 deletions(-) > > diff --git a/block/iscsi.c b/block/iscsi.c > index 14e97a6..f19a56a 100644 > --- a/block/iscsi.c > +++ b/block/iscsi.c > @@ -69,6 +69,7 @@ typedef struct IscsiLun { > bool dpofua; > bool has_write_same; > bool force_next_flush; > + bool request_timed_out; > } IscsiLun; > > typedef struct IscsiTask { > @@ -99,7 +100,8 @@ typedef struct IscsiAIOCB { > #endif > } IscsiAIOCB; > > -#define EVENT_INTERVAL 250 > +/* libiscsi uses time_t so its enough to process events every second */ > +#define EVENT_INTERVAL 1000 > #define NOP_INTERVAL 5000 > #define MAX_NOP_FAILURES 3 > #define ISCSI_CMD_RETRIES ARRAY_SIZE(iscsi_retry_times) > @@ -186,13 +188,18 @@ iscsi_co_generic_cb(struct iscsi_context *iscsi, int > status, > iTask->do_retry = 1; > goto out; > } > - /* status 0x28 is SCSI_TASK_SET_FULL. It was first introduced > - * in libiscsi 1.10.0. Hardcode this value here to avoid > - * the need to bump the libiscsi requirement to 1.10.0 */ > - if (status == SCSI_STATUS_BUSY || status == 0x28) { > + if (status == SCSI_STATUS_BUSY || status == > SCSI_STATUS_TIMEOUT || > + status == SCSI_STATUS_TASK_SET_FULL) { > unsigned retry_time = > exp_random(iscsi_retry_times[iTask->retries - 1]); > - error_report("iSCSI Busy/TaskSetFull (retry #%u in %u > ms): %s", > + if (status == SCSI_STATUS_TIMEOUT) { > + /* make sure the request is rescheduled AFTER the > + * reconnect is initiated */ > + retry_time = EVENT_INTERVAL * 2; > + iTask->iscsilun->request_timed_out = true; > + } > + error_report("iSCSI Busy/TaskSetFull/TimeOut" > + " (retry #%u in %u ms): %s", > iTask->retries, retry_time, > iscsi_get_error(iscsi)); > aio_timer_init(iTask->iscsilun->aio_context, > @@ -276,20 +283,26 @@ iscsi_set_events(IscsiLun *iscsilun) > iscsilun); > iscsilun->events = ev; > } > - > - /* newer versions of libiscsi may return zero events. In this > - * case start a timer to ensure we are able to return to service > - * once this situation changes. */ > - if (!ev) { > - timer_mod(iscsilun->event_timer, > - qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + > EVENT_INTERVAL); > - } > } > > -static void iscsi_timed_set_events(void *opaque) > +static void iscsi_timed_check_events(void *opaque) > { > IscsiLun *iscsilun = opaque; > + > + /* check for timed out requests */ > + iscsi_service(iscsilun->iscsi, 0); > + > + if (iscsilun->request_timed_out) { > + iscsilun->request_timed_out = false; > + iscsi_reconnect(iscsilun->iscsi); > + } > + > + /* newer versions of libiscsi may return zero events. Ensure we are > able > + * to return to service once this situation changes. */ > iscsi_set_events(iscsilun); > + > + timer_mod(iscsilun->event_timer, > + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + EVENT_INTERVAL); > } > > static void > @@ -1096,16 +1109,37 @@ static char *parse_initiator_name(const char > *target) > return iscsi_name; > } > > +static int parse_timeout(const char *target) > +{ > + QemuOptsList *list; > + QemuOpts *opts; > + const char *timeout; > + > + list = qemu_find_opts("iscsi"); > + if (list) { > + opts = qemu_opts_find(list, target); > + if (!opts) { > + opts = QTAILQ_FIRST(&list->head); > + } > + if (opts) { > + timeout = qemu_opt_get(opts, "timeout"); > + if (timeout) { > + return atoi(timeout); > + } > + } > + } > + > + return 0; > +} > + > static void iscsi_nop_timed_event(void *opaque) > { > IscsiLun *iscsilun = opaque; > > - if (iscsi_get_nops_in_flight(iscsilun->iscsi) > MAX_NOP_FAILURES) { > + if (iscsi_get_nops_in_flight(iscsilun->iscsi) >= MAX_NOP_FAILURES) { > error_report("iSCSI: NOP timeout. Reconnecting..."); > - iscsi_reconnect(iscsilun->iscsi); > - } > - > - if (iscsi_nop_out_async(iscsilun->iscsi, NULL, NULL, 0, NULL) != 0) { > + iscsilun->request_timed_out = true; > + } else if (iscsi_nop_out_async(iscsilun->iscsi, NULL, NULL, 0, NULL) > != 0) { > error_report("iSCSI: failed to sent NOP-Out. Disabling NOP > messages."); > return; > } > @@ -1263,10 +1297,13 @@ static void > iscsi_attach_aio_context(BlockDriverState *bs, > timer_mod(iscsilun->nop_timer, > qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + NOP_INTERVAL); > > - /* Prepare a timer for a delayed call to iscsi_set_events */ > + /* Set up a timer for periodic calls to iscsi_set_events and to > + * scan for command timeout */ > iscsilun->event_timer = aio_timer_new(iscsilun->aio_context, > QEMU_CLOCK_REALTIME, SCALE_MS, > - iscsi_timed_set_events, > iscsilun); > + iscsi_timed_check_events, > iscsilun); > + timer_mod(iscsilun->event_timer, > + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + EVENT_INTERVAL); > } > > static void iscsi_modesense_sync(IscsiLun *iscsilun) > @@ -1391,6 +1428,8 @@ static int iscsi_open(BlockDriverState *bs, QDict > *options, int flags, > goto out; > } > > + iscsi_set_timeout(iscsi, parse_timeout(iscsi_url->target)); > + > if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) > != 0) { > error_setg(errp, "iSCSI: Failed to connect to LUN : %s", > iscsi_get_error(iscsi)); > @@ -1739,6 +1778,10 @@ static QemuOptsList qemu_iscsi_opts = { > .name = "initiator-name", > .type = QEMU_OPT_STRING, > .help = "Initiator iqn name to use when connecting", > + },{ > + .name = "timeout", > + .type = QEMU_OPT_NUMBER, > + .help = "Request timeout in seconds (default 0 = no timeout)", > }, > { /* end of list */ } > }, > diff --git a/configure b/configure > index 222694f..2ed9db2 100755 > --- a/configure > +++ b/configure > @@ -3660,15 +3660,15 @@ if compile_prog "" "" ; then > fi > > ########################################## > -# Do we have libiscsi >= 1.9.0 > +# Do we have libiscsi >= 1.10.0 > if test "$libiscsi" != "no" ; then > - if $pkg_config --atleast-version=1.9.0 libiscsi; then > + if $pkg_config --atleast-version=1.10.0 libiscsi; then > libiscsi="yes" > libiscsi_cflags=$($pkg_config --cflags libiscsi) > libiscsi_libs=$($pkg_config --libs libiscsi) > else > if test "$libiscsi" = "yes" ; then > - feature_not_found "libiscsi" "Install libiscsi >= 1.9.0" > + feature_not_found "libiscsi" "Install libiscsi >= 1.10.0" > fi > libiscsi="no" > fi > diff --git a/qemu-options.hx b/qemu-options.hx > index bf1683e..ca37481 100644 > --- a/qemu-options.hx > +++ b/qemu-options.hx > @@ -2291,6 +2291,9 @@ By default qemu will use the iSCSI initiator-name > 'iqn.2008-11.org.linux-kvm[:<name>]' but this can also be set from the > command > line or a configuration file. > > +Since version Qemu 2.4 it is possible to specify a iSCSI request timeout > to detect > +stalled requests and force a reestablishment of the session. The timeout > +is specified in seconds. The default is 0 which means no timeout. > > Example (without authentication): > @example > @@ -2318,6 +2321,7 @@ DEF("iscsi", HAS_ARG, QEMU_OPTION_iscsi, > "-iscsi [user=user][,password=password]\n" > " [,header-digest=CRC32C|CR32C-NONE|NONE-CRC32C|NONE\n" > " [,initiator-name=initiator-iqn][,id=target-iqn]\n" > + " [,timeout=timeout]\n" > " iSCSI session parameters\n", QEMU_ARCH_ALL) > STEXI > > -- > 1.9.1 > >