Skip to content
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

Fix crash weak assocobject in viewcontroller #162

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions .github/workflows/run-danger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,12 @@ on:

jobs:
build:
runs-on: macos-14
runs-on: ubuntu-latest
name: "Run Danger"
steps:
- uses: actions/checkout@v4

- name: Select Xcode
# See https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
run: |
sudo xcode-select -s /Applications/Xcode_15.4.app
xcodebuild -version

- name: Install Danger Swift
run: |
if ! which danger-swift > /dev/null; then
echo "Danger-swift is not installed; We'll try to install it."

if ! which brew > /dev/null; then
echo "Brew is not installed; cannot proceed with Danger installation."
fi

brew bundle --verbose

echo "Danger was installed successfully"
else
echo "Danger-swift is already installed"
fi

danger-swift --version

- name: Run Danger
run: danger-swift ci
- name: Run Danger Swift
uses: danger/swift@3.20.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
115 changes: 81 additions & 34 deletions Sources/EmbraceCore/Capture/UX/View/UIViewControllerHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,34 +90,40 @@ class UIViewControllerHandler {
// We generate the id here, outside of the `queue`, to ensure we're doing it while the ViewController is still alive (renerding process).
// There could be a race condition and it's possible that the controller was released or is in the process of deallocation,
// which could cause a crash (as this feature relies on objc_setAssociatedObject).
// This kind of operation should be done _for anything_ that accesses the UIViewController.
let id = UUID().uuidString
let state = ViewInstrumentationState()
state.viewDidLoadSpanCreated = true
state.identifier = id
vc.emb_instrumentation_state = state

let className = vc.className
let viewName = vc.emb_viewName

// check if with need to measure time-to-render or time-to-interactive
let nameFormat = vc is InteractableViewController ?
SpanSemantics.View.timeToInteractiveName :
SpanSemantics.View.timeToFirstRenderName

queue.async {
// generate parent span
let className = vc.className

// check if with need to measure time-to-render or time-to-interactive
let nameFormat = vc is InteractableViewController ?
SpanSemantics.View.timeToInteractiveName :
SpanSemantics.View.timeToFirstRenderName

let spanName = nameFormat.replacingOccurrences(of: "NAME", with: className)

let parentSpan = self.createSpan(
with: otel,
vc: vc,
viewName: viewName,
className: className,
name: spanName,
startTime: now
)

// generate view did load span
let viewDidLoadSpan = self.createSpan(
with: otel,
vc: vc,
viewName: viewName,
className: className,
name: SpanSemantics.View.viewDidLoadName,
startTime: now,
parent: parentSpan
Expand All @@ -129,9 +135,11 @@ class UIViewControllerHandler {
}

func onViewDidLoadEnd(_ vc: UIViewController, now: Date = Date()) {
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}
queue.async {
guard let id = vc.emb_instrumentation_state?.identifier,
let span = self.viewDidLoadSpans.removeValue(forKey: id) else {
guard let span = self.viewDidLoadSpans.removeValue(forKey: id) else {
return
}

Expand All @@ -140,18 +148,26 @@ class UIViewControllerHandler {
}

func onViewWillAppearStart(_ vc: UIViewController, now: Date = Date()) {
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}

vc.emb_instrumentation_state?.viewWillAppearSpanCreated = true

let className = vc.className
let viewName = vc.emb_viewName

queue.async {
guard let otel = self.dataSource?.otel,
let id = vc.emb_instrumentation_state?.identifier,
let parentSpan = self.parentSpans[id] else {
return
}

// generate view will appear span
let span = self.createSpan(
with: otel,
vc: vc,
viewName: viewName,
className: className,
name: SpanSemantics.View.viewWillAppearName,
startTime: now,
parent: parentSpan
Expand All @@ -162,9 +178,11 @@ class UIViewControllerHandler {
}

func onViewWillAppearEnd(_ vc: UIViewController, now: Date = Date()) {
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}
queue.async {
guard let id = vc.emb_instrumentation_state?.identifier,
let span = self.viewWillAppearSpans.removeValue(forKey: id) else {
guard let span = self.viewWillAppearSpans.removeValue(forKey: id) else {
return
}

Expand All @@ -173,18 +191,26 @@ class UIViewControllerHandler {
}

func onViewIsAppearingStart(_ vc: UIViewController, now: Date = Date()) {
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}

vc.emb_instrumentation_state?.viewIsAppearingSpanCreated = true

let className = vc.className
let viewName = vc.emb_viewName

queue.async {
guard let otel = self.dataSource?.otel,
let id = vc.emb_instrumentation_state?.identifier,
let parentSpan = self.parentSpans[id] else {
return
}

// generate view is appearing span
let span = self.createSpan(
with: otel,
vc: vc,
viewName: viewName,
className: className,
name: SpanSemantics.View.viewIsAppearingName,
startTime: now,
parent: parentSpan
Expand All @@ -206,18 +232,26 @@ class UIViewControllerHandler {
}

func onViewDidAppearStart(_ vc: UIViewController, now: Date = Date()) {
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}

vc.emb_instrumentation_state?.viewDidAppearSpanCreated = true

let className = vc.className
let viewName = vc.emb_viewName

queue.async {
guard let otel = self.dataSource?.otel,
let id = vc.emb_instrumentation_state?.identifier,
let parentSpan = self.parentSpans[id] else {
return
}

// generate view did appear span
let span = self.createSpan(
with: otel,
vc: vc,
viewName: viewName,
className: className,
name: SpanSemantics.View.viewDidAppearName,
startTime: now,
parent: parentSpan
Expand All @@ -238,16 +272,25 @@ class UIViewControllerHandler {
}
}

guard let id = vc.emb_instrumentation_state?.identifier else {
// This should never happen
return
}

let className = vc.className
let viewName = vc.emb_viewName

queue.async {
guard let otel = self.dataSource?.otel, let id = vc.emb_instrumentation_state?.identifier else {
guard let otel = self.dataSource?.otel else {
return
}

// check if we need to create a visibility span
if self.dataSource?.instrumentVisibility == true {
let span = self.createSpan(
with: otel,
vc: vc,
viewName: viewName,
className: className,
name: SpanSemantics.View.screenName,
type: .view,
startTime: now
Expand All @@ -266,13 +309,14 @@ class UIViewControllerHandler {
// end time to first render span
if parentSpan.isTimeToFirstRender {
parentSpan.end(time: now)
self.clear(id: id, vc: vc)
self.clear(id: id)

// generate ui ready span
} else {
let span = self.createSpan(
with: otel,
vc: vc,
viewName: viewName,
className: className,
name: SpanSemantics.View.uiReadyName,
startTime: now,
parent: parentSpan
Expand All @@ -284,7 +328,7 @@ class UIViewControllerHandler {
span.end(time: now)
parentSpan.end(time: now)

self.clear(id: id, vc: vc)
self.clear(id: id)

// otherwise we save it to close it later
} else {
Expand All @@ -295,11 +339,11 @@ class UIViewControllerHandler {
}

func onViewDidDisappear(_ vc: UIViewController) {
queue.async {
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}

queue.async {
let now = Date()

// end visibility span
Expand All @@ -314,9 +358,12 @@ class UIViewControllerHandler {
}

func onViewBecameInteractive(_ vc: UIViewController) {
guard let id = vc.emb_instrumentation_state?.identifier else {
return
}

queue.async {
guard let id = vc.emb_instrumentation_state?.identifier,
let parentSpan = self.parentSpans[id],
guard let parentSpan = self.parentSpans[id],
parentSpan.isTimeToInteractive else {
return
}
Expand All @@ -327,7 +374,7 @@ class UIViewControllerHandler {
let now = Date()
span.end(time: now)
parentSpan.end(time: now)
self.clear(id: id, vc: vc)
self.clear(id: id)

// otherwise it means the view is still loading, in this case we flag
// the view controller so we can close the spans as soon as
Expand Down Expand Up @@ -369,7 +416,8 @@ class UIViewControllerHandler {

private func createSpan(
with otel: EmbraceOpenTelemetry,
vc: UIViewController,
viewName: String,
className: String,
name: String,
type: SpanType = .viewLoad,
startTime: Date,
Expand All @@ -379,8 +427,8 @@ class UIViewControllerHandler {
name: name,
type: type,
attributes: [
SpanSemantics.View.keyViewTitle: vc.emb_viewName,
SpanSemantics.View.keyViewName: vc.className
SpanSemantics.View.keyViewTitle: viewName,
SpanSemantics.View.keyViewName: className
],
autoTerminationCode: nil
)
Expand All @@ -394,15 +442,14 @@ class UIViewControllerHandler {
return builder.startSpan()
}

private func clear(id: String, vc: UIViewController? = nil) {
private func clear(id: String) {
self.parentSpans[id] = nil
self.viewDidLoadSpans[id] = nil
self.viewWillAppearSpans[id] = nil
self.viewIsAppearingSpans[id] = nil
self.viewDidAppearSpans[id] = nil
self.uiReadySpans[id] = nil
self.alreadyFinishedUiReadyIds.remove(id)
vc?.emb_instrumentation_state = nil
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final class ViewCaptureService: CaptureService, UIViewControllerHandlerDa
}

var instrumentFirstRender: Bool {
return options.instrumentFirstRender
return options.instrumentFirstRender && Embrace.client?.config?.isUiLoadInstrumentationEnabled == true
}

@objc public convenience init(options: ViewCaptureService.Options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ class DefaultLogBatcherTests: XCTestCase {
givenDefaultLogBatcher(limits: .init(maxBatchAge: 0.1, maxLogsPerBatch: 10))
givenRepositoryCreatesLogsSuccessfully()
whenInvokingAddLogRecord(withLogRecord: randomLogRecord())
thenDelegateShouldInvokeBatchFinishedAfterBatchLifespan(0.2)
thenDelegateShouldInvokeBatchFinishedAfterBatchLifespan(0.5)
self.delegate.didCallBatchFinished = false
whenInvokingAddLogRecord(withLogRecord: randomLogRecord())
thenDelegateShouldInvokeBatchFinishedAfterBatchLifespan(0.2)
thenDelegateShouldInvokeBatchFinishedAfterBatchLifespan(0.5)
}

func testAutoEndBatchAfterLifespanExpired_CancelWhenBatchEndedPrematurely() {
Expand Down
Loading