| 129 | _tickInterval = tickInterval; |
| 130 | } |
| 131 | |
| 132 | void JavaScriptANRDetector::onNextTick(DispatchFunction nextTickCallback) { |
| 133 | std::lock_guard<Mutex> lock(_mutex); |
| 134 | _nextTickCallbacks.emplace_back(std::move(nextTickCallback)); |
| 135 | } |
| 136 | |
| 137 | void JavaScriptANRDetector::scheduleNextTick() { |
| 138 | if (_nextTickTask != 0 || !_started || !_isInForeground) { |
| 139 | return; |
| 140 | } |
| 141 | _nextTickTask = _dispatchQueue->asyncAfter( |
| 142 | [self = Valdi::weakRef(this)]() { |
| 143 | auto strongSelf = self.lock(); |
| 144 | if (strongSelf != nullptr) { |
| 145 | strongSelf->checkForANRs(); |
| 146 | } |
| 147 | }, |
| 148 | _tickInterval); |
| 149 | } |
| 150 | |
| 151 | bool JavaScriptANRDetector::onANR(JavaScriptTaskScheduler& taskScheduler, |
| 152 | std::chrono::steady_clock::duration detectionThreshold, |
| 153 | const std::atomic<bool>& ack) { |
| 154 | // Read the native-side attribution before waiting on the stack capture. The capture can take up |
| 155 | // to kCaptureStacktraceTimeoutSeconds, and a native call or module load that finishes during |
| 156 | // that wait clears its name, which would report the stall as unattributed. Both getters read |
| 157 | // saved native state, not JS, so they are safe to call while the JS thread is stuck. |
| 158 | auto loadingModule = taskScheduler.getCurrentlyLoadingModule(); |
| 159 | auto attributionInfo = taskScheduler.getANRAttributionInfo(); |
| 160 | auto stacktraces = taskScheduler.captureStackTraces(std::chrono::seconds(kCaptureStacktraceTimeoutSeconds)); |
| 161 | StringBox moduleName; |
| 162 | std::string message; |
| 163 | |
| 164 | if (!stacktraces.empty() && stacktraces[0].getContext() != nullptr) { |
| 165 | moduleName = stacktraces[0].getContext()->getPath().getResourceId().bundleName; |
| 166 | } |
| 167 | |
| 168 | // Module loads run under the global context (no bundle name) and expose no JS stack while the |
| 169 | // bundle is being parsed, so a load that holds the JS thread past the threshold would |
| 170 | // otherwise be reported unattributed. Attribute it to the bundle being loaded instead. |
| 171 | bool isLoadingModule = false; |
| 172 | if (moduleName.isEmpty()) { |
| 173 | moduleName = loadingModule; |
| 174 | isLoadingModule = !moduleName.isEmpty(); |
| 175 | } |
| 176 | |
| 177 | if (!hasRunningStacktrace(stacktraces) && ack.load()) { |
| 178 | // The ack was submitted while waiting to capture stacktraces. |
| 179 | // We consider that task scheduler has recovered |
| 180 | return true; |
| 181 | } |
| 182 | |
| 183 | auto detectionThresholdString = |
| 184 | snap::utils::time::Duration<std::chrono::steady_clock>(detectionThreshold).toString(); |
| 185 | if (moduleName.isEmpty()) { |
| 186 | message = fmt::format("Detected unattributed ANR after {}", detectionThresholdString); |
| 187 | } else if (isLoadingModule) { |
| 188 | message = |
nothing calls this directly
no test coverage detected