cifs: handle when server stops supporting multichannel
When a server stops supporting multichannel, we will keep attempting reconnects to the secondary channels today. Avoid this by freeing extra channels when negotiate returns no multichannel support. Signed-off-by: Shyam Prasad N <sprasad@microsoft.com> Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
parent
705fc522fe
commit
ee1d21794e
6 changed files with 145 additions and 10 deletions
|
@ -650,6 +650,7 @@ struct TCP_Server_Info {
|
||||||
bool noautotune; /* do not autotune send buf sizes */
|
bool noautotune; /* do not autotune send buf sizes */
|
||||||
bool nosharesock;
|
bool nosharesock;
|
||||||
bool tcp_nodelay;
|
bool tcp_nodelay;
|
||||||
|
bool terminate;
|
||||||
unsigned int credits; /* send no more requests at once */
|
unsigned int credits; /* send no more requests at once */
|
||||||
unsigned int max_credits; /* can override large 32000 default at mnt */
|
unsigned int max_credits; /* can override large 32000 default at mnt */
|
||||||
unsigned int in_flight; /* number of requests on the wire to server */
|
unsigned int in_flight; /* number of requests on the wire to server */
|
||||||
|
|
|
@ -641,6 +641,8 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses,
|
||||||
bool
|
bool
|
||||||
cifs_chan_is_iface_active(struct cifs_ses *ses,
|
cifs_chan_is_iface_active(struct cifs_ses *ses,
|
||||||
struct TCP_Server_Info *server);
|
struct TCP_Server_Info *server);
|
||||||
|
void
|
||||||
|
cifs_disable_secondary_channels(struct cifs_ses *ses);
|
||||||
int
|
int
|
||||||
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
|
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
|
||||||
int
|
int
|
||||||
|
|
|
@ -219,6 +219,14 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
|
||||||
|
|
||||||
spin_lock(&cifs_tcp_ses_lock);
|
spin_lock(&cifs_tcp_ses_lock);
|
||||||
list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
|
list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
|
||||||
|
/*
|
||||||
|
* if channel has been marked for termination, nothing to do
|
||||||
|
* for the channel. in fact, we cannot find the channel for the
|
||||||
|
* server. So safe to exit here
|
||||||
|
*/
|
||||||
|
if (server->terminate)
|
||||||
|
break;
|
||||||
|
|
||||||
/* check if iface is still active */
|
/* check if iface is still active */
|
||||||
if (!cifs_chan_is_iface_active(ses, server))
|
if (!cifs_chan_is_iface_active(ses, server))
|
||||||
cifs_chan_update_iface(ses, server);
|
cifs_chan_update_iface(ses, server);
|
||||||
|
@ -253,6 +261,8 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
|
||||||
spin_lock(&tcon->tc_lock);
|
spin_lock(&tcon->tc_lock);
|
||||||
tcon->status = TID_NEED_RECON;
|
tcon->status = TID_NEED_RECON;
|
||||||
spin_unlock(&tcon->tc_lock);
|
spin_unlock(&tcon->tc_lock);
|
||||||
|
|
||||||
|
cancel_delayed_work(&tcon->query_interfaces);
|
||||||
}
|
}
|
||||||
if (ses->tcon_ipc) {
|
if (ses->tcon_ipc) {
|
||||||
ses->tcon_ipc->need_reconnect = true;
|
ses->tcon_ipc->need_reconnect = true;
|
||||||
|
|
|
@ -296,6 +296,60 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
|
||||||
return new_chan_count - old_chan_count;
|
return new_chan_count - old_chan_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* called when multichannel is disabled by the server.
|
||||||
|
* this always gets called from smb2_reconnect
|
||||||
|
* and cannot get called in parallel threads.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
cifs_disable_secondary_channels(struct cifs_ses *ses)
|
||||||
|
{
|
||||||
|
int i, chan_count;
|
||||||
|
struct TCP_Server_Info *server;
|
||||||
|
struct cifs_server_iface *iface;
|
||||||
|
|
||||||
|
spin_lock(&ses->chan_lock);
|
||||||
|
chan_count = ses->chan_count;
|
||||||
|
if (chan_count == 1)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
ses->chan_count = 1;
|
||||||
|
|
||||||
|
/* for all secondary channels reset the need reconnect bit */
|
||||||
|
ses->chans_need_reconnect &= 1;
|
||||||
|
|
||||||
|
for (i = 1; i < chan_count; i++) {
|
||||||
|
iface = ses->chans[i].iface;
|
||||||
|
server = ses->chans[i].server;
|
||||||
|
|
||||||
|
if (iface) {
|
||||||
|
spin_lock(&ses->iface_lock);
|
||||||
|
kref_put(&iface->refcount, release_iface);
|
||||||
|
ses->chans[i].iface = NULL;
|
||||||
|
iface->num_channels--;
|
||||||
|
if (iface->weight_fulfilled)
|
||||||
|
iface->weight_fulfilled--;
|
||||||
|
spin_unlock(&ses->iface_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock(&ses->chan_lock);
|
||||||
|
if (server && !server->terminate) {
|
||||||
|
server->terminate = true;
|
||||||
|
cifs_signal_cifsd_for_reconnect(server, false);
|
||||||
|
}
|
||||||
|
spin_lock(&ses->chan_lock);
|
||||||
|
|
||||||
|
if (server) {
|
||||||
|
ses->chans[i].server = NULL;
|
||||||
|
cifs_put_tcp_session(server, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
spin_unlock(&ses->chan_lock);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* update the iface for the channel if necessary.
|
* update the iface for the channel if necessary.
|
||||||
* will return 0 when iface is updated, 1 if removed, 2 otherwise
|
* will return 0 when iface is updated, 1 if removed, 2 otherwise
|
||||||
|
@ -595,14 +649,10 @@ cifs_ses_add_channel(struct cifs_ses *ses,
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (rc && chan->server) {
|
if (rc && chan->server) {
|
||||||
/*
|
cifs_put_tcp_session(chan->server, 0);
|
||||||
* we should avoid race with these delayed works before we
|
|
||||||
* remove this channel
|
|
||||||
*/
|
|
||||||
cancel_delayed_work_sync(&chan->server->echo);
|
|
||||||
cancel_delayed_work_sync(&chan->server->reconnect);
|
|
||||||
|
|
||||||
spin_lock(&ses->chan_lock);
|
spin_lock(&ses->chan_lock);
|
||||||
|
|
||||||
/* we rely on all bits beyond chan_count to be clear */
|
/* we rely on all bits beyond chan_count to be clear */
|
||||||
cifs_chan_clear_need_reconnect(ses, chan->server);
|
cifs_chan_clear_need_reconnect(ses, chan->server);
|
||||||
ses->chan_count--;
|
ses->chan_count--;
|
||||||
|
@ -612,8 +662,6 @@ out:
|
||||||
*/
|
*/
|
||||||
WARN_ON(ses->chan_count < 1);
|
WARN_ON(ses->chan_count < 1);
|
||||||
spin_unlock(&ses->chan_lock);
|
spin_unlock(&ses->chan_lock);
|
||||||
|
|
||||||
cifs_put_tcp_session(chan->server, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kfree(ctx->UNC);
|
kfree(ctx->UNC);
|
||||||
|
|
|
@ -164,6 +164,8 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
||||||
struct nls_table *nls_codepage = NULL;
|
struct nls_table *nls_codepage = NULL;
|
||||||
struct cifs_ses *ses;
|
struct cifs_ses *ses;
|
||||||
int xid;
|
int xid;
|
||||||
|
struct TCP_Server_Info *pserver;
|
||||||
|
unsigned int chan_index;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
|
* SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
|
||||||
|
@ -224,6 +226,12 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* if server is marked for termination, cifsd will cleanup */
|
||||||
|
if (server->terminate) {
|
||||||
|
spin_unlock(&server->srv_lock);
|
||||||
|
return -EHOSTDOWN;
|
||||||
|
}
|
||||||
spin_unlock(&server->srv_lock);
|
spin_unlock(&server->srv_lock);
|
||||||
|
|
||||||
again:
|
again:
|
||||||
|
@ -242,12 +250,24 @@ again:
|
||||||
tcon->need_reconnect);
|
tcon->need_reconnect);
|
||||||
|
|
||||||
mutex_lock(&ses->session_mutex);
|
mutex_lock(&ses->session_mutex);
|
||||||
|
/*
|
||||||
|
* if this is called by delayed work, and the channel has been disabled
|
||||||
|
* in parallel, the delayed work can continue to execute in parallel
|
||||||
|
* there's a chance that this channel may not exist anymore
|
||||||
|
*/
|
||||||
|
spin_lock(&server->srv_lock);
|
||||||
|
if (server->tcpStatus == CifsExiting) {
|
||||||
|
spin_unlock(&server->srv_lock);
|
||||||
|
mutex_unlock(&ses->session_mutex);
|
||||||
|
rc = -EHOSTDOWN;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Recheck after acquire mutex. If another thread is negotiating
|
* Recheck after acquire mutex. If another thread is negotiating
|
||||||
* and the server never sends an answer the socket will be closed
|
* and the server never sends an answer the socket will be closed
|
||||||
* and tcpStatus set to reconnect.
|
* and tcpStatus set to reconnect.
|
||||||
*/
|
*/
|
||||||
spin_lock(&server->srv_lock);
|
|
||||||
if (server->tcpStatus == CifsNeedReconnect) {
|
if (server->tcpStatus == CifsNeedReconnect) {
|
||||||
spin_unlock(&server->srv_lock);
|
spin_unlock(&server->srv_lock);
|
||||||
mutex_unlock(&ses->session_mutex);
|
mutex_unlock(&ses->session_mutex);
|
||||||
|
@ -284,6 +304,53 @@ again:
|
||||||
|
|
||||||
rc = cifs_negotiate_protocol(0, ses, server);
|
rc = cifs_negotiate_protocol(0, ses, server);
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
|
/*
|
||||||
|
* if server stopped supporting multichannel
|
||||||
|
* and the first channel reconnected, disable all the others.
|
||||||
|
*/
|
||||||
|
if (ses->chan_count > 1 &&
|
||||||
|
!(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
|
||||||
|
if (SERVER_IS_CHAN(server)) {
|
||||||
|
cifs_dbg(VFS, "server %s does not support " \
|
||||||
|
"multichannel anymore. skipping secondary channel\n",
|
||||||
|
ses->server->hostname);
|
||||||
|
|
||||||
|
spin_lock(&ses->chan_lock);
|
||||||
|
chan_index = cifs_ses_get_chan_index(ses, server);
|
||||||
|
if (chan_index == CIFS_INVAL_CHAN_INDEX) {
|
||||||
|
spin_unlock(&ses->chan_lock);
|
||||||
|
goto skip_terminate;
|
||||||
|
}
|
||||||
|
|
||||||
|
ses->chans[chan_index].server = NULL;
|
||||||
|
spin_unlock(&ses->chan_lock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the above reference of server by channel
|
||||||
|
* needs to be dropped without holding chan_lock
|
||||||
|
* as cifs_put_tcp_session takes a higher lock
|
||||||
|
* i.e. cifs_tcp_ses_lock
|
||||||
|
*/
|
||||||
|
cifs_put_tcp_session(server, 1);
|
||||||
|
|
||||||
|
server->terminate = true;
|
||||||
|
cifs_signal_cifsd_for_reconnect(server, false);
|
||||||
|
|
||||||
|
/* mark primary server as needing reconnect */
|
||||||
|
pserver = server->primary_server;
|
||||||
|
cifs_signal_cifsd_for_reconnect(pserver, false);
|
||||||
|
|
||||||
|
skip_terminate:
|
||||||
|
mutex_unlock(&ses->session_mutex);
|
||||||
|
rc = -EHOSTDOWN;
|
||||||
|
goto out;
|
||||||
|
} else {
|
||||||
|
cifs_server_dbg(VFS, "does not support " \
|
||||||
|
"multichannel anymore. disabling all other channels\n");
|
||||||
|
cifs_disable_secondary_channels(ses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rc = cifs_setup_session(0, ses, server, nls_codepage);
|
rc = cifs_setup_session(0, ses, server, nls_codepage);
|
||||||
if ((rc == -EACCES) && !tcon->retry) {
|
if ((rc == -EACCES) && !tcon->retry) {
|
||||||
mutex_unlock(&ses->session_mutex);
|
mutex_unlock(&ses->session_mutex);
|
||||||
|
@ -3836,6 +3903,13 @@ void smb2_reconnect_server(struct work_struct *work)
|
||||||
/* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
|
/* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
|
||||||
mutex_lock(&pserver->reconnect_mutex);
|
mutex_lock(&pserver->reconnect_mutex);
|
||||||
|
|
||||||
|
/* if the server is marked for termination, drop the ref count here */
|
||||||
|
if (server->terminate) {
|
||||||
|
cifs_put_tcp_session(server, true);
|
||||||
|
mutex_unlock(&pserver->reconnect_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
INIT_LIST_HEAD(&tmp_list);
|
INIT_LIST_HEAD(&tmp_list);
|
||||||
INIT_LIST_HEAD(&tmp_ses_list);
|
INIT_LIST_HEAD(&tmp_ses_list);
|
||||||
cifs_dbg(FYI, "Reconnecting tcons and channels\n");
|
cifs_dbg(FYI, "Reconnecting tcons and channels\n");
|
||||||
|
|
|
@ -1023,7 +1023,7 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses)
|
||||||
spin_lock(&ses->chan_lock);
|
spin_lock(&ses->chan_lock);
|
||||||
for (i = 0; i < ses->chan_count; i++) {
|
for (i = 0; i < ses->chan_count; i++) {
|
||||||
server = ses->chans[i].server;
|
server = ses->chans[i].server;
|
||||||
if (!server)
|
if (!server || server->terminate)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Add table
Reference in a new issue