【问题标题】:SharePoint CSOM request hangsSharePoint CSOM 请求挂起
【发布时间】:2021-10-22 04:36:12
【问题描述】:

这很罕见,但已经出现过好几次了。这次下载文件的请求被卡住了,我们的软件已经挂了将近 17 个小时。我们采用了 ProcDump,它表明在“Socket.Receive”调用期间发生了停顿,这是Microsoft.SharePoint.Client.ClientContext.ExecuteQuery 调用的结果。

这是ProcDump 堆栈跟踪:

ChildEBP RetAddr  Caller, Callee
08a5de44 6f45a7b5 mswsock!SockWaitForSingleObject+0x125, calling ntdll_77a80000!NtWaitForSingleObject
08a5dea0 6f46c7a8 mswsock!WSPRecv+0x2e8, calling mswsock!SockWaitForSingleObject
08a5df14 75681560 ws2_32!recv+0x100
08a5df68 72bccbff (MethodDesc 729f4eb4 +0x3b DomainBoundILStubClass.IL_STUB_PInvoke(IntPtr, Byte*, Int32, System.Net.Sockets.SocketFlags))
08a5df90 72bccbff (MethodDesc 729f4eb4 +0x3b DomainBoundILStubClass.IL_STUB_PInvoke(IntPtr, Byte*, Int32, System.Net.Sockets.SocketFlags))
08a5dfac 72b6c3dd (MethodDesc 729e2b04 +0xbd System.Net.Sockets.Socket.Receive(Byte[], Int32, Int32, System.Net.Sockets.SocketFlags, System.Net.Sockets.SocketError ByRef)), calling 72aed3fc
08a5dfd4 72b6c2ce (MethodDesc 729e2af8 +0x1e System.Net.Sockets.Socket.Receive(Byte[], Int32, Int32, System.Net.Sockets.SocketFlags)), calling (MethodDesc 729e2b04 +0 System.Net.Sockets.Socket.Receive(Byte[], Int32, Int32, System.Net.Sockets.SocketFlags, System.Net.Sockets.SocketError ByRef))
08a5dffc 72b6c203 (MethodDesc 729e0590 +0x83 System.Net.Sockets.NetworkStream.Read(Byte[], Int32, Int32)), calling (MethodDesc 729e2af8 +0 System.Net.Sockets.Socket.Receive(Byte[], Int32, Int32, System.Net.Sockets.SocketFlags))
08a5e030 72b5335d (MethodDesc 729e0ad0 +0x21 System.Net.FixedSizeReader.ReadPacket(Byte[], Int32, Int32))
08a5e04c 72b9b65e (MethodDesc 72a3fcb8 +0xa2 System.Net.Security._SslStream.StartFrameBody(Int32, Byte[], Int32, Int32, System.Net.AsyncProtocolRequest)), calling (MethodDesc 729e0ad0 +0 System.Net.FixedSizeReader.ReadPacket(Byte[], Int32, Int32))
08a5e06c 72b9b3ba (MethodDesc 72a3fcac +0x92 System.Net.Security._SslStream.StartFrameHeader(Byte[], Int32, Int32, System.Net.AsyncProtocolRequest)), calling (MethodDesc 72a3fcb8 +0 System.Net.Security._SslStream.StartFrameBody(Int32, Byte[], Int32, Int32, System.Net.AsyncProtocolRequest))
08a5e094 72b9b087 (MethodDesc 72a3fca0 +0x77 System.Net.Security._SslStream.StartReading(Byte[], Int32, Int32, System.Net.AsyncProtocolRequest)), calling (MethodDesc 72a3fcac +0 System.Net.Security._SslStream.StartFrameHeader(Byte[], Int32, Int32, System.Net.AsyncProtocolRequest))
08a5e0bc 72b9af60 (MethodDesc 729e0374 +0xc8 System.Net.Security._SslStream.ProcessRead(Byte[], Int32, Int32, System.Net.AsyncProtocolRequest)), calling (MethodDesc 72a3fca0 +0 System.Net.Security._SslStream.StartReading(Byte[], Int32, Int32, System.Net.AsyncProtocolRequest))
08a5e0fc 72b532f3 (MethodDesc 72a4170c +0x4f System.Net.TlsStream.Read(Byte[], Int32, Int32)), calling (MethodDesc 729e0374 +0 System.Net.Security._SslStream.ProcessRead(Byte[], Int32, Int32, System.Net.AsyncProtocolRequest))
08a5e12c 72b6c0d0 (MethodDesc 72a3d738 +0x68 System.Net.PooledStream.Read(Byte[], Int32, Int32))
08a5e164 73216b6c (MethodDesc 72a3aa78 System.Net.ChunkParser.HandlePayload())
08a5e188 72b8ec41 (MethodDesc 729db7ec +0x3d System.Net.ChunkParser.ProcessResponse()), calling (MethodDesc 72a3aa78 +0 System.Net.ChunkParser.HandlePayload())
08a5e19c 730428a7 (MethodDesc 72a3aa24 +0x2f System.Net.ChunkParser.Read(Byte[], Int32, Int32)), calling (MethodDesc 729db7ec +0 System.Net.ChunkParser.ProcessResponse())
08a5e1c0 7320b446 (MethodDesc 72a3ee4c System.Net.ConnectStream.ReadWithoutValidation(Byte[], Int32, Int32, Boolean)), calling (MethodDesc 72a3aa24 +0 System.Net.ChunkParser.Read(Byte[], Int32, Int32))
08a5e1f4 72b6d885 (MethodDesc 729dff68 +0xe5 System.Net.ConnectStream.Read(Byte[], Int32, Int32)), calling (MethodDesc 72a3ee4c +0 System.Net.ConnectStream.ReadWithoutValidation(Byte[], Int32, Int32, Boolean))
08a5e230 013b8087 (MethodDesc 013849a0 +0xb7 Microsoft.SharePoint.Client.Mime.BufferedReadStream.Read(Byte[], Int32, Int32))
08a5e250 013b7eb2 (MethodDesc 0826e348 +0x2a Microsoft.SharePoint.Client.Mime.DelimitedStreamReader.Read(DelimitedReadStream, Byte[], Int32, Int32))
08a5e26c 013b7d08 (MethodDesc 0826e6a0 +0x48 Microsoft.SharePoint.Client.Mime.DelimitedStreamReader+DelimitedReadStream.Read(Byte[], Int32, Int32)), calling (MethodDesc 0826e348 +0 Microsoft.SharePoint.Client.Mime.DelimitedStreamReader.Read(DelimitedReadStream, Byte[], Int32, Int32))
08a5e28c 013b9c95 (MethodDesc 08d5ef3c +0x45 Microsoft.SharePoint.Client.ChunkStreamBuilder.CopyFrom(System.IO.Stream))
08a5e2a8 096da950 (MethodDesc 08d5abf8 +0x2a0 Microsoft.SharePoint.Client.ClientRequest.ProcessResponse()), calling (MethodDesc 08d5ef3c +0 Microsoft.SharePoint.Client.ChunkStreamBuilder.CopyFrom(System.IO.Stream))
08a5e2f8 096da228 (MethodDesc 08d5ac1c +0xc8 Microsoft.SharePoint.Client.ClientRequest.ExecuteQueryToServer(Microsoft.SharePoint.Client.ChunkStringBuilder)), calling (MethodDesc 08d5abf8 +0 Microsoft.SharePoint.Client.ClientRequest.ProcessResponse())
08a5e324 08d4cf9d (MethodDesc 08d5956c +0x18d Microsoft.SharePoint.Client.ClientContext.ExecuteQuery()), calling (MethodDesc 08d5ac1c +0 Microsoft.SharePoint.Client.ClientRequest.ExecuteQueryToServer(Microsoft.SharePoint.Client.ChunkStringBuilder))
...our code which calls Microsoft.SharePoint.Client.ClientContext.ExecuteQuery()

我们可以用这个做些什么吗?或者我们应该依靠微软在他们的 CSOM 库中解决这个问题?

注意:我们使用版本 16.1.19927.12000 的 Microsoft.SharePointOnline.CSOM 库 (https://www.nuget.org/packages/Microsoft.SharePointOnline.CSOM/16.1.19927.12000)。

RequestTimeout 未明确设置,因此应使用默认值(3 分钟)。我们通常会时不时地看到超时,这很正常。但我们不希望看到永远挂起的请求。

编辑:

我有一些关于这个问题的更新。上次发生这种情况时,我们没有停止该过程并决定等待。在 32.5 小时 的等待之后,代码终于抛出了 IOException,我们能够完成这个过程。

  • 下载文件请求于 2021-08-25 16:29:33.6426877 发送。
  • 代码在“System.Net.Sockets.Socket.Receive”行中停留了 32.5 小时。
  • 2021-08-27 01:11:23.5161189 抛出异常

IOException:无法从传输连接读取数据:一个 现有连接被远程主机强行关闭。

我们使用的是 .NET 4.6.1。

【问题讨论】:

  • 拜托,可以分享你的代码方法吗?
  • 连接是通过 TLS 1.0 还是 TLS 1.2 建立的?

标签: c# .net sockets sharepoint csom


【解决方案1】:

没有任何代码示例,我将不得不做出一些使用假设。如果您以与此类似的方式使用它,我们至少可以使用取消令牌打破摊位。然后在你认为合适的情况下,你可以处理它。它可能就像重试一样简单,但如果不调试行为就很难说。

    try
    {
        var cancelToken = new CancellationTokenSource();
        var ms = 600_000;
        cancelToken.CancelAfter(ms);
        await ExecuteQueryWithTimeout(cancelToken);
    }
    catch (TaskCanceledException)
    {
        Console.WriteLine("Tasks cancelled: timed out");
    }
    finally
    {
        cancelToken.Dispose();
    }

ExecuteQuery 的异步方法

private static async Task ExecuteQueryWithTimeout(CancellationToken token)
{
    await Task.Run(()=> {
      Uri uri = new Uri(folderUrl);
      SP.Folder folder = web.GetFolderByServerRelativeUrl(uri.AbsolutePath);
      context.Load(folder);
      context.ExecuteQuery();
    });
}
           

【讨论】:

  • 谢谢,如果我不知道 ExecuteQuery 进程内部发生了什么,我也在考虑类似的事情。在我看来,这就像微软代码中的一个错误,我什至向他们开了一张支持票......
  • 由于没有其他答案可以说明问题的根本原因,我会将这个答案标记为已接受并奖励赏金。谢谢@bjlasc01。但是,我仍然想知道幕后发生了什么,以及这是否在新的 CSOM 库版本中得到了解决。
【解决方案2】:

我能想到几个原因:-

  1. 正在通过 TLS 1.0 建立连接。尝试安装操作系统更新,更新 .NET 框架,通过显式设置通过 TLS 1.2 建立连接。参考:https://docs.microsoft.com/en-us/dotnet/framework/network-programming/tls

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

  1. 检查防火墙规则以确保它们没有阻止连接

【讨论】:

  • 我认为在 .NET > 4.6 中,他们已经使用 4.5 版本中的 TLS 修复了该错误。所以我不确定这是否会有所帮助。
【解决方案3】:

最近,我通过 CSOM 方法在 SharePoint 中通过查询大量文件递归地抛出文件夹和子文件夹(超过 9000 个文件)遇到了这个问题,经过长时间的研究,我遇到了这个 Microsoft 文档,其中包含解释以及如何解决这个问题:Avoid getting throttled or blocked in SharePoint Online

我使用此 Microsoft Docs 文章建议的扩展方法提取此代码。

public static class CsomExtensions
{
    public static void ExecuteQueryWithIncrementalRetry(this ClientContext clientContext, int retryCount, int delay)
    {
        int retryAttempts = 0;
        int backoffInterval = delay;
        int retryAfterInterval = 0;
        bool retry = false;
        ClientRequestWrapper wrapper = null;
        if (retryCount <= 0)
            throw new ArgumentException("Provide a retry count greater than zero.");
        if (delay <= 0)
            throw new ArgumentException("Provide a delay greater than zero.");

        // Do while retry attempt is less than retry count
        while (retryAttempts < retryCount)
        {
            try
            {
                if (!retry)
                {
                    clientContext.ExecuteQuery();
                    return;
                }
                else
                {
                    //increment the retry count
                    retryAttempts++;

                    // retry the previous request using wrapper
                    if (wrapper != null && wrapper.Value != null)
                    {
                        clientContext.RetryQuery(wrapper.Value);
                        return;
                    }
                    // retry the previous request as normal
                    else
                    {
                        clientContext.ExecuteQuery();
                        return;
                    }
                }
            }
            catch (WebException ex)
            {
                var response = ex.Response as HttpWebResponse;
                // Check if request was throttled - http status code 429
                // Check is request failed due to server unavailable - http status code 503
                if (response != null && (response.StatusCode == (HttpStatusCode)429 || response.StatusCode == (HttpStatusCode)503))
                {
                    wrapper = (ClientRequestWrapper)ex.Data["ClientRequest"];
                    retry = true;

                    // Determine the retry after value - use the `Retry-After` header when available
                    string retryAfterHeader = response.GetResponseHeader("Retry-After");
                    if (!string.IsNullOrEmpty(retryAfterHeader))
                    {
                        if (!Int32.TryParse(retryAfterHeader, out retryAfterInterval))
                        {
                            retryAfterInterval = backoffInterval;
                        }
                    }
                    else
                    {
                        retryAfterInterval = backoffInterval;
                    }

                    // Delay for the requested seconds
                    Thread.Sleep(retryAfterInterval * 1000);

                    // Increase counters
                    backoffInterval = backoffInterval * 2;
                }
                else
                {
                    throw;
                }
            }
        }
        throw new MaximumRetryAttemptedException($"Maximum retry attempts {retryCount}, has be attempted.");
    }
}

[Serializable]
public class MaximumRetryAttemptedException : Exception
{
    public MaximumRetryAttemptedException(string message) : base(message) { }
}

【讨论】:

  • 感谢@Antonio 的回答。不幸的是,我们的问题与限制无关。我们已经在我们的代码中使用了类似的方法,但仍然遇到这个 SO 问题中描述的问题。
  • @ChuckNorris,抱歉没有详细说明我的答案,但这不仅适用于限制问题,而且如果您需要一种弹性方法来连接到 SharePoint Online。
猜你喜欢
  • 2021-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
  • 2022-12-12
  • 2017-01-06
  • 2016-08-09
  • 1970-01-01
相关资源
最近更新 更多