DF-5026 / fix.diff
diff --git a/sys/dev/raid/ips/ips_disk.c b/sys/dev/raid/ips/ips_disk.c --- a/sys/dev/raid/ips/ips_disk.c +++ b/sys/dev/raid/ips/ips_disk.c @@ -129,10 +129,28 @@ { cdev_t dev = ap->a_head.a_dev; struct bio *bio = ap->a_bio; + struct buf *bp = bio->bio_buf; ipsdisk_softc_t *dsc; dsc = dev->si_drv1; DEVICE_PRINTF(8, dsc->dev, "in strategy\n"); + /* + * Fail fast when the adapter has been declared dead: once + * IPS_OFFLINE is set (ips_timeout() / ips_cmdqueue_free()), + * ips_get_free_cmd() returns EIO and ips_start_io_request() + * silently drops the bio on the floor -- no biodone() ever + * happens, wedging every issuer in an unkillable D-state and + * growing sc->bio_queue without bound. (This check is advisory + * and racy by design; the authoritative drain lives in + * ips_start_io_request(), see the companion ips_commands.c fix.) + */ + if (dsc->sc->state & IPS_OFFLINE) { + bp->b_flags |= B_ERROR; + bp->b_error = EIO; + bp->b_resid = bp->b_bcount; + biodone(bio); + return (0); + } bio->bio_driver_info = dsc; devstat_start_transaction(&dsc->stats); lockmgr(&dsc->sc->queue_lock, LK_EXCLUSIVE|LK_RETRY); diff --git a/sys/dev/raid/ips/ips_commands.c b/sys/dev/raid/ips/ips_commands.c --- a/sys/dev/raid/ips/ips_commands.c +++ b/sys/dev/raid/ips/ips_commands.c @@ -239,12 +239,38 @@ { ips_command_t *command; struct bio *bio; + int error; bio = bioq_first(&sc->bio_queue); if (bio == NULL) return; - if (ips_get_free_cmd(sc, &command, 0) != 0) + error = ips_get_free_cmd(sc, &command, 0); + if (error == EBUSY) + /* + * Transient (all command slots busy, or a timeout-driven + * reset in progress): the bio stays queued and is retried + * on the next completion. This behavior is correct. + */ return; + if (error != 0) { + /* + * Terminal: the adapter is IPS_OFFLINE and will never + * complete anything again. Fail every queued bio now -- + * previously they were parked in bio_queue forever with + * no biodone(), wedging the issuers permanently and + * growing the queue without bound. + */ + while ((bio = bioq_first(&sc->bio_queue)) != NULL) { + struct buf *bp; + bioq_remove(&sc->bio_queue, bio); + bp = bio->bio_buf; + bp->b_flags |= B_ERROR; + bp->b_error = error; + bp->b_resid = bp->b_bcount; + biodone(bio); + } + return; + } bioq_remove(&sc->bio_queue, bio); if (bio->bio_buf->b_cmd == BUF_CMD_FLUSH) ips_send_flush_request(command, bio); |