-
-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: blocking detection #2709
feat: blocking detection #2709
Changes from all commits
5a29e10
17bd0bf
a25993c
7173711
ef951da
e1173fa
579fc98
43515be
d83362b
047c0dc
fc1d708
869168f
9859b21
3fa8364
3cb5bb6
50f5b6d
85509fd
c75eb29
0c15b10
0c855f4
663658f
dcd5680
669d3b9
39fb85c
3d74dde
c549b92
679e099
48d186b
a00a4a5
e2ac986
578532e
c593f82
4d5b8e1
fb5602f
b315b1a
39926f4
81e1951
4e732f4
2e8b05a
df2a8d6
42ed4a8
c87472e
1cf1d18
45c67b9
f8d872a
9846d5a
322afff
80be4dd
ffef982
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Sample usages of the Sentry SDK for ASP.NET Core on an MVC app | ||
|
||
Start by changing the DSN in `appsettings.json`, `Sentry:Dsn` property with your own. | ||
No DSN yet? Get one for free at https://sentry.io/ to give this sample a run. | ||
|
||
Blocking detection: | ||
* It's turned on via option `CaptureBlockingCalls` | ||
* In the `HomeController` there's an action that causes a blocking call on an async method. | ||
* You can trigger it with: | ||
* `GET http://localhost:5001/home/block/true` | ||
|
||
Results `Was blocking? True` and an event captured in Sentry. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using Sentry.Internal; | ||
using Sentry.Protocol; | ||
|
||
// Namespace starting with Sentry makes sure the SDK cuts frames off before reporting | ||
namespace Sentry.Ben.BlockingDetector | ||
{ | ||
internal class BlockingMonitor : IBlockingMonitor | ||
{ | ||
private readonly Func<IHub> _getHub; | ||
private readonly SentryOptions _options; | ||
internal readonly IRecursionTracker _recursionTracker; | ||
|
||
public BlockingMonitor(Func<IHub> getHub, SentryOptions options) | ||
: this(getHub, options, new StaticRecursionTracker()) | ||
{ | ||
} | ||
|
||
internal BlockingMonitor(Func<IHub> getHub, SentryOptions options, IRecursionTracker recursionTracker) | ||
{ | ||
_getHub = getHub; | ||
_options = options; | ||
_recursionTracker = recursionTracker; | ||
} | ||
|
||
private static bool ShouldSkipFrame(string? frameInfo) => | ||
frameInfo?.StartsWith("Sentry.Ben") == true | ||
// Skip frames relating to the TaskBlockingListener | ||
|| frameInfo?.StartsWith("System.Diagnostics") == true | ||
// Skip frames relating to the async state machine | ||
|| frameInfo?.StartsWith("System.Threading") == true; | ||
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can skip doing this and improve grouping on the server side for this kind of stuff in general. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bitsandfoxes could you link to the associated PR where this logic gets changed on the server? Also, if we do skip this here, we'll need to make some changes to set a custom fingerprint that excludes all this stuff (since by default the entire stacktrace is used as the fingerprint). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll follow up on this. The blocking detection is opt-in and the improvements to the grouping are not blocking this PR. #3202 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If we remove the logic above from the client, the grouping will be broken unless some similar logic exists on the server. I had a chat to Bruno though... Not sure we need to be doing this on the server. |
||
|
||
public void BlockingStart(DetectionSource detectionSource) | ||
{ | ||
// From Stephen Cleary: | ||
// "The default SynchronizationContext queues its asynchronous delegates to the ThreadPool but executes its | ||
// synchronous delegates directly on the calling thread." | ||
// | ||
// Implicitly then, if we're not on a ThreadPool thread, we're not in an async context. | ||
if (!Thread.CurrentThread.IsThreadPoolThread) | ||
{ | ||
return; | ||
} | ||
|
||
_recursionTracker.Recurse(); | ||
|
||
try | ||
{ | ||
if (!_recursionTracker.IsFirstRecursion()) | ||
{ | ||
return; | ||
} | ||
|
||
var stackTrace = DebugStackTrace.Create( | ||
_options, | ||
new StackTrace(true), | ||
true, | ||
ShouldSkipFrame | ||
); | ||
var evt = new SentryEvent | ||
{ | ||
Level = SentryLevel.Warning, | ||
Message = | ||
"Blocking method has been invoked and blocked, this can lead to ThreadPool starvation. Learn more about it: " + | ||
"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices#avoid-blocking-calls ", | ||
SentryExceptions = new[] | ||
{ | ||
new SentryException | ||
{ | ||
ThreadId = Environment.CurrentManagedThreadId, | ||
Mechanism = new Mechanism | ||
{ | ||
Type = "BlockingCallDetector", | ||
Handled = false, | ||
Description = "Blocking calls can cause ThreadPool starvation.", | ||
Source = detectionSource.ToString() | ||
}, | ||
Type = "Blocking call detected", | ||
Stacktrace = stackTrace, | ||
} | ||
}, | ||
}; | ||
|
||
_getHub().CaptureEvent(evt); | ||
} | ||
catch | ||
{ | ||
// ignored | ||
} | ||
} | ||
|
||
public void BlockingEnd() | ||
{ | ||
if (!Thread.CurrentThread.IsThreadPoolThread) | ||
{ | ||
return; | ||
} | ||
|
||
_recursionTracker.Backtrack(); | ||
} | ||
} | ||
|
||
internal enum DetectionSource | ||
{ | ||
SynchronizationContext, | ||
EventListener | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bruno-garcia won't the SynchronizationContext in ASP.NET Core always be null? Is this code here just in case SDK users have implemented a custom sync context for some reason?