Commit 08535466 authored by Lars Ellenberg's avatar Lars Ellenberg Committed by Jens Axboe

drbd: evaluate disk and network timeout on different requests

Just because it is the oldest not yet completed request
does not make it the oldest request waiting for disk.
Or waiting for the peer.

And we completely missed already completed requests
that would still hold references to activity log extents,
waiting only for the barrier ack.

Find two oldest not yet completely processed requests,
one that is still waiting for local completion,
and one that is still waiting for some response from the peer.
These may or may not be the same request object.

Then separately apply the network and disk timeouts, respectively.
Signed-off-by: default avatarPhilipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: default avatarLars Ellenberg <lars.ellenberg@linbit.com>
Signed-off-by: default avatarJens Axboe <axboe@fb.com>
parent 67cca286
...@@ -1353,23 +1353,35 @@ int drbd_merge_bvec(struct request_queue *q, struct bvec_merge_data *bvm, struct ...@@ -1353,23 +1353,35 @@ int drbd_merge_bvec(struct request_queue *q, struct bvec_merge_data *bvm, struct
return limit; return limit;
} }
static struct drbd_request *find_oldest_request(struct drbd_connection *connection) static void find_oldest_requests(
struct drbd_connection *connection,
struct drbd_device *device,
struct drbd_request **oldest_req_waiting_for_peer,
struct drbd_request **oldest_req_waiting_for_disk)
{ {
/* Walk the transfer log,
* and find the oldest not yet completed request */
struct drbd_request *r; struct drbd_request *r;
*oldest_req_waiting_for_peer = NULL;
*oldest_req_waiting_for_disk = NULL;
list_for_each_entry(r, &connection->transfer_log, tl_requests) { list_for_each_entry(r, &connection->transfer_log, tl_requests) {
if (atomic_read(&r->completion_ref)) const unsigned s = r->rq_state;
return r; if (!*oldest_req_waiting_for_peer
&& ((s & RQ_NET_MASK) && !(s & RQ_NET_DONE)))
*oldest_req_waiting_for_peer = r;
if (!*oldest_req_waiting_for_disk
&& (s & RQ_LOCAL_PENDING) && r->device == device)
*oldest_req_waiting_for_disk = r;
if (*oldest_req_waiting_for_peer && *oldest_req_waiting_for_disk)
break;
} }
return NULL;
} }
void request_timer_fn(unsigned long data) void request_timer_fn(unsigned long data)
{ {
struct drbd_device *device = (struct drbd_device *) data; struct drbd_device *device = (struct drbd_device *) data;
struct drbd_connection *connection = first_peer_device(device)->connection; struct drbd_connection *connection = first_peer_device(device)->connection;
struct drbd_request *req; /* oldest request */ struct drbd_request *req_disk, *req_peer; /* oldest request */
struct net_conf *nc; struct net_conf *nc;
unsigned long ent = 0, dt = 0, et, nt; /* effective timeout = ko_count * timeout */ unsigned long ent = 0, dt = 0, et, nt; /* effective timeout = ko_count * timeout */
unsigned long now; unsigned long now;
...@@ -1393,8 +1405,8 @@ void request_timer_fn(unsigned long data) ...@@ -1393,8 +1405,8 @@ void request_timer_fn(unsigned long data)
now = jiffies; now = jiffies;
spin_lock_irq(&device->resource->req_lock); spin_lock_irq(&device->resource->req_lock);
req = find_oldest_request(connection); find_oldest_requests(connection, device, &req_peer, &req_disk);
if (!req) { if (req_peer == NULL && req_disk == NULL) {
spin_unlock_irq(&device->resource->req_lock); spin_unlock_irq(&device->resource->req_lock);
mod_timer(&device->request_timer, now + et); mod_timer(&device->request_timer, now + et);
return; return;
...@@ -1416,19 +1428,26 @@ void request_timer_fn(unsigned long data) ...@@ -1416,19 +1428,26 @@ void request_timer_fn(unsigned long data)
* ~198 days with 250 HZ, we have a window where the timeout would need * ~198 days with 250 HZ, we have a window where the timeout would need
* to expire twice (worst case) to become effective. Good enough. * to expire twice (worst case) to become effective. Good enough.
*/ */
if (ent && req->rq_state & RQ_NET_PENDING && if (ent && req_peer &&
time_after(now, req->start_time + ent) && time_after(now, req_peer->start_time + ent) &&
!time_in_range(now, connection->last_reconnect_jif, connection->last_reconnect_jif + ent)) { !time_in_range(now, connection->last_reconnect_jif, connection->last_reconnect_jif + ent)) {
drbd_warn(device, "Remote failed to finish a request within ko-count * timeout\n"); drbd_warn(device, "Remote failed to finish a request within ko-count * timeout\n");
_drbd_set_state(_NS(device, conn, C_TIMEOUT), CS_VERBOSE | CS_HARD, NULL); _drbd_set_state(_NS(device, conn, C_TIMEOUT), CS_VERBOSE | CS_HARD, NULL);
} }
if (dt && req->rq_state & RQ_LOCAL_PENDING && req->device == device && if (dt && req_disk &&
time_after(now, req->start_time + dt) && time_after(now, req_disk->start_time + dt) &&
!time_in_range(now, device->last_reattach_jif, device->last_reattach_jif + dt)) { !time_in_range(now, device->last_reattach_jif, device->last_reattach_jif + dt)) {
drbd_warn(device, "Local backing device failed to meet the disk-timeout\n"); drbd_warn(device, "Local backing device failed to meet the disk-timeout\n");
__drbd_chk_io_error(device, DRBD_FORCE_DETACH); __drbd_chk_io_error(device, DRBD_FORCE_DETACH);
} }
nt = (time_after(now, req->start_time + et) ? now : req->start_time) + et;
/* Reschedule timer for the nearest not already expired timeout.
* Fallback to now + min(effective network timeout, disk timeout). */
ent = (ent && req_peer && time_before(now, req_peer->start_time + ent))
? req_peer->start_time + ent : now + et;
dt = (dt && req_disk && time_before(now, req_disk->start_time + dt))
? req_disk->start_time + dt : now + et;
nt = time_before(ent, dt) ? ent : dt;
spin_unlock_irq(&connection->resource->req_lock); spin_unlock_irq(&connection->resource->req_lock);
mod_timer(&device->request_timer, nt); mod_timer(&device->request_timer, nt);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment