From: Aurelien Aptel <aaptel@xxxxxxxx> Check channel credits to prevent the client from using a starved channel that cannot send anything. Special care must be taken in selecting the minimum value: when channels are created they start off with a small amount that slowly ramps up as the channel gets used. Thus a new channel might never be picked if the min value is too small. Signed-off-by: Aurelien Aptel <aaptel@xxxxxxxx> --- fs/cifs/transport.c | 57 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c index e90a1d1380b0..7bb1584b3724 100644 --- a/fs/cifs/transport.c +++ b/fs/cifs/transport.c @@ -44,6 +44,14 @@ /* Max number of iovectors we can use off the stack when sending requests. */ #define CIFS_MAX_IOV_SIZE 8 +/* + * Min number of credits for a channel to be picked. + * + * Note that once a channel reaches this threshold it will never be + * picked again as no credits can be requested from it. + */ +#define CIFS_CHANNEL_MIN_CREDITS 3 + void cifs_wake_up_task(struct mid_q_entry *mid) { @@ -1051,20 +1059,53 @@ cifs_cancelled_callback(struct mid_q_entry *mid) struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses) { uint index = 0; + struct TCP_Server_Info *s; if (!ses) return NULL; - if (!ses->binding) { - /* round robin */ - if (ses->chan_count > 1) { - index = (uint)atomic_inc_return(&ses->chan_seq); - index %= ses->chan_count; - } - return ses->chans[index].server; - } else { + if (ses->binding) return cifs_ses_server(ses); + + /* + * Channels are created right after the session is made. The + * count cannot change after that so it is not racy to check. + */ + if (ses->chan_count == 1) + return ses->chans[index].server; + + /* round robin */ + index = (uint)atomic_inc_return(&ses->chan_seq); + index %= ses->chan_count; + s = ses->chans[index].server; + + /* + * Checking server credits is racy, but getting a slightly + * stale value should not be an issue here + */ + if (s->credits <= CIFS_CHANNEL_MIN_CREDITS) { + uint i; + + cifs_dbg(VFS, "cannot pick conn_id=0x%llx not enough credits (%u)", + s->conn_id, + s->credits); + + /* + * Look at all other channels starting from the next + * one and pick first possible channel. + */ + for (i = 1; i < ses->chan_count; i++) { + s = ses->chans[(index+i) % ses->chan_count].server; + if (s->credits > CIFS_CHANNEL_MIN_CREDITS) + return s; + } } + + /* + * If none are possible, keep the initially picked one, but + * later on it will block to wait for credits or fail. + */ + return ses->chans[index].server; } int -- 2.30.0