">
 

The Notification That Ate My MacBook: A Debugging Story

Iniciado por joomlamz, Hoje at 06:25

Respostas: 1   |   Visualizações: 1

Tópico anterior - Tópico seguinte

0 Membros e 1 Visitante estão a ver este tópico.

Saudações, malta do **webmastersmz.com**! Daqui fala o vosso especialista em tecnologia.

Hoje trago-vos uma análise detalhada de um artigo que tem dado muito que falar na comunidade global de *sysadmins* e desenvolvedores: **"The Notification That Ate My MacBook: A Debugging Story"**. Esta é uma daquelas histórias de "terror" tecnológico que qualquer um de nós, aqui em Moçambique, seja em Maputo, Beira ou Nampula, pode enfrentar enquanto tenta entregar um projeto dentro do *deadline*.

Vamos dissecar tecnicamente o que aconteceu e tirar lições valiosas para o nosso dia-a-dia no desenvolvimento e administração de sistemas.

---

### Análise Técnica: O que realmente aconteceu?

O artigo relata um problema bizarro onde um MacBook moderno e potente começa a travar, com as ventoinhas a parecerem um *machimbombo* antigo na subida da Namaacha, tudo por causa de... uma simples notificação pendente.

Aqui estão os pontos principais e mais ricos desta jornada de *debugging*:

#### 1. O Sintoma: CPU e RAM no Limite (`WindowServer` e `NotificationCenter`)
O autor deparou-se com o sistema extremamente lento. Ao abrir o Monitor de Atividade (ou correr o comando `top` no terminal), percebeu que os processos `WindowServer` e `NotificationCenter` estavam a consumir quase 100% da CPU e uma quantidade absurda de memória RAM (um clássico *memory leak*).

#### 2. O Processo de Investigação (*Troubleshooting*)
Em vez de simplesmente reiniciar a máquina — que é o que 90% dos utilizadores comuns fariam — o autor agiu como um verdadeiro engenheiro de sistemas. Ele usou ferramentas de diagnóstico nativas do macOS, como o comando `sample`, para analisar o que aquela *thread* específica estava a processar no momento do travamento.

#### 3. A Causa Raiz: Corrupção na Base de Dados SQLite
Descobriu-se que o subsistema de notificações do macOS guarda o histórico e o estado das notificações numa base de dados SQLite local (geralmente escondida algures em `/private/var/folders/...`).
O que aconteceu foi que uma notificação específica (provavelmente mal formatada ou interrompida a meio do registo) corrompeu esta base de dados. O processo `NotificationCenter` entrou num *loop* infinito de leitura/escrita tentando processar aquele registo corrompido, gerando o gargalo gigante.

#### 4. A Solução Técnica
A solução não passou por formatar o Mac. O autor localizou o ficheiro `.db` corrompido do SQLite, eliminou-o via Terminal e reiniciou o processo do Notification Center. O macOS, ao ver que a base de dados não existia, recriou uma nova, limpa e funcional, resolvendo o problema instantaneamente.

---

### Vamos ao Debate no webmastersmz.com!

Esta história deixa-nos várias lições e quero muito ver a vossa opinião lá no fórum:

1. **Até que ponto confiamos nos sistemas modernos?** Às vezes pensamos que o ecossistema da Apple é "infalível", mas este caso prova que uma simples falha num ficheiro SQLite pode mandar um computador de última geração abaixo.
2. **Vocês documentam os vossos bugs?** Quantos de nós já resolveram problemas bicudos em servidores ou máquinas de desenvolvimento locais e não documentaram? Se o autor não tivesse escrito isto, o próximo gajo a sofrer com isto ia perder dias a reinstalar o SO.
3. **Quais são as vossas ferramentas de eleição para monitoria de processos em ambiente local (Mac/Linux)?** Usam mais o Terminal ou ferramentas visuais?

Deixem os vossos comentários lá no tópico do **webmastersmz.com**. Vamos partilhar experiências sobre os piores *memory leaks* que já tiveram de caçar na vossa carreira!

---

E claro, malta, para garantir que os vossos projetos, aplicações e fóruns rodam sem falhas e sem esses gargalos que nos tiram o sono, convido-vos a conhecer as soluções de alojamento de alta performance da **AplicHost** em **https://aplichost.com**. Eles garantem servidores robustos e um suporte sempre pronto a ajudar-vos a resolver qualquer mistério tecnológico!

The Notification That Ate My MacBook: A Debugging Story



Tópico: The Notification That Ate My MacBook: A Debugging Story
Categoria: Tutoriais | Programação & Tecnologia
Idioma Principal: Português (Conteúdo de Tecnologia)

Descrição do Conteúdo / Informações:
-------------------------------------------------------------------------
How a deleted-in-the-end time-tracking app pinned a macOS daemon at 99% CPU for who knows how long — and how every "obvious" fix failed before a one-line log command cracked it open.



📖 It started with the wrong question


"My Mac is heating up. What's using all the RAM?"

That's how it began — a warm laptop, a whirring fan, and the completely reasonable assumption that memory was the villain. It's the question everyone asks, and it's almost always the wrong one.

Thirty seconds of ps and memory_pressure later, the RAM theory was dead: 65% free memory, zero swap used. Nothing was starving. But sorting processes by CPU instead of memory turned up something strange sitting quietly at the top:

99.7  36928   635  /usr/sbin/usernoted

usernoted. The macOS notification daemon. A background process whose entire job is to pop little banners in the corner of your screen — burning an entire CPU core, continuously, while using a laughable 16 MB of RAM.

That was the heat. Not Chrome with its forty tabs. Not VS Code. A system daemon nobody has ever consciously interacted with.

📝 Note: heat is a CPU problem wearing a RAM costume. Fans respond to power draw, and power draw follows CPU cycles. When a machine runs hot, sort by CPU first.



🐛 The two-second respawn


The classic move — killall usernoted — worked for exactly two seconds. The daemon relaunched (they always do; launchd sees to that) and was back at 97% before the shell prompt returned.

That respawn told us something important: this wasn't a transient glitch or a stuck request. Something persistent — data sitting on disk — was feeding the spin. Kill the process all you want; it reloads the same poison on every boot.

So the question changed from "what process?" to "what data?"



🔍 Reading the daemon's mind


macOS ships a wonderful and underused tool called sample, which snapshots a process's call stacks. Pointed at the spinning daemon, it returned the smoking mechanism in beautiful, damning detail:

-[UNCalendarNotificationTrigger nextTriggerDateAfterDate:withRequestedDate:]
Calendar.nextDate(after:matching:options:)
Calendar._enumerateDatesStep(...)
Calendar.dateAfterMatchingWeekOfYear(...)

Translation: somewhere in the notification store sat a scheduled repeating notification whose date rule the system could not resolve. Foundation's calendar engine was trying to compute "when does this fire next?" — and looping forever on the week-of-year component. Not crashing. Not erroring. Just spinning, politely and eternally, at 99%.

We had the murder weapon. We did not have the murderer.



❌ The graveyard of reasonable theories


Here's where the hunt got humbling. The stack trace screamed "calendar," so we did what any sane person would do — we went after calendar data. One suspect at a time, with a proper measurement after each kill (more on that later):

Suspect #1: A Google account synced to Calendar. Google-synced recurring events with exotic rules are a known source of cursed date math. Removed the account. Still pinned at 96%.

Suspect #2: Calendar.app's notification toggle. Flipped off in System Settings. Still pinned. (And a lesson hiding inside: the toggle suppresses delivery — it doesn't unschedule triggers already sitting in the store.)

Suspect #3: A suspect calendar, deleted outright. Still pinned.

Suspects #4–6: Three leftover calendars from old task-manager apps, unsubscribed in one sweep. Task and todo apps that "integrate with Apple Calendar" love writing recurring events with alerts, and uninstalling the app leaves the events behind. A genuinely great theory. Still pinned.

Suspect #7: The "Work" calendar itself, exported to .ics and grepped for BYWEEKNO — the one recurrence keyword that maps exactly onto the week-of-year loop in the stack. Ninety-three events, every recurrence rule bounded and boring. Clean.

Meanwhile, every attempt to just read the notification store directly hit a wall. The legacy database at com.apple.notificationcenter/db2/db? Zero bytes — deprecated, empty, a decoy that half the internet's fix-it guides still tell you to delete. The real store? Locked behind macOS's privacy walls, unreadable even after granting Full Disk Access, because the Calendar data sits behind its own separate permission category.

Six suspects. Six alibis. The daemon spun on, byte-for-byte the same stack trace, as if mocking the entire investigation.



💡 The one-line command that broke the case


At this point the user — tired of watching calendars get executed for crimes they didn't commit — made a simple suggestion:

"Did you try log show --predicate 'process == \"usernoted\"' --info --last 5m? Check once."

I had. Hours earlier. It had returned nothing, and I'd written the log avenue off.

Except — it hadn't returned nothing. It had never run at all.

Running it this time, without error-suppression, the shell coughed up the truth:

(eval):log:1: too many arguments

The session shell was zsh, and zsh has a builtin command named log — an ancient login-watching utility — which had been silently shadowing /usr/bin/log the entire time. Every one of my earlier log queries had failed instantly, and my tidy little 2>/dev/null had swallowed the error, converting "command failed" into "logs are empty." I had abandoned the single most direct diagnostic in the toolbox because of a name collision and my own muffler on stderr.

One character path fix — /usr/bin/log show ... — and the culprit was on screen in under five seconds:

NotificationRecord app:"com.glimsoft.Timelines"
req:"Timelines-engagement-longtail-one"
category:"GSNotificationCategory_Reengagement"
...authorizationStatus: Denied...

Repeated. Dozens of times per second.



🎯 The culprit: a nag that couldn't take no for an answer


Timelines — a time-tracking app, long forgotten, still sitting in /Applications, not even running — had at some point scheduled two repeating "re-engagement" notifications. The names say everything: engagement-longtail-one and engagement-longtail-two. These are the "hey, come back and use me!" nags that growth teams schedule far into the future.

And here's the poison pill: at some point, the user had denied the app notification permission. Perfectly normal thing to do. But the scheduled requests didn't get cleaned up — they stayed in the daemon's store, repeating triggers for notifications that could never be shown. So usernoted sat in an eternal cycle: compute the next fire date (spinning through the pathological week-of-year path), attempt delivery, get refused by the permission system, reschedule, repeat. Forever. On every boot.

Not a calendar event. Not a Google sync artifact. A re-engagement notification from an unused app, colliding with a denied permission, feeding a genuine infinite-loop bug in Apple's date-matching code.

The trap in the stack trace, in hindsight: UNCalendarNotificationTrigger is the API class any app uses to schedule a date-based local notification. The word "calendar" in the class name refers to calendar math, not Calendar-the-app. The stack named the mechanism precisely and pointed at the owner not at all — and we spent hours interrogating the app whose name happened to match.



✅ The kill, verified


Delete /Applications/Timelines.app. Restart the daemon. Measure:

before:  8.0s of CPU time per 8s window   (~99%, sustained, every boot)
after:   0.38s total, zero growth          (idle)
loop frames in stack sample: 0

Fan quiet. Lap cool. Case closed.



🛠️ TL;DR — if your Mac is hot and usernoted (or disnoted) is the culprit



Confirm it's spinning (not a snapshot artifact) by diffing CPU time over a window:

PID=$(pgrep -x usernoted); T1=$(ps -o time= -p $PID); sleep 10; ps -o time= -p $PID

Near-equal = idle. Grew ~10s = pinned.

• See what it's stuck on: sample $(pgrep -x usernoted) 3


Ask it who's to blame (full path — see the zsh gotcha above):

/usr/bin/log show --predicate 'process == "usernoted"' --info --last 5m

• Look for one bundle ID repeating relentlessly, especially a repeating notification with authorizationStatus: Denied. Delete/fix that app. Done — no reboot, no database wipe.



📚 What this hunt actually teaches


Empty output is only evidence if the command actually ran. 2>/dev/null turns failures into false negatives — the most expensive kind, because they close doors that were open. Never suppress stderr on the first run of a diagnostic. And beware shell builtins shadowing real binaries: log, time, stat, print. When in doubt, command -v or the absolute path.

A stack trace names the mechanism, not the owner. Before acting on a class name in a stack, ask: who can instantiate this? If the answer is "anyone," go find attribution data — logs, records, receipts — before executing suspects.

Measure with counters, not snapshots. top -l 1 lied to us mid-hunt, showing 0.0% for a fully pinned process (the first top sample has no interval to diff against). The measurement that never lies: read the process's cumulative CPU time, sleep N seconds, read it again. A monotonic counter differenced over wall-clock time cannot be fooled.

Uninstalled ≠ gone, and denied ≠ deleted. Apps leave scheduled notifications, calendar events, and login items behind like fingerprints. And denying a permission doesn't purge the requests that permission would have served — it can, apparently, turn them into tiny perpetual-motion machines.

And the meta-lesson: the breakthrough came from the person who said "did you actually try the log?" — re-running a supposedly exhausted avenue and refusing to trust a silent result. The best debugging move of the whole session wasn't a clever command. It was skepticism about a negative.

⚠️ Warning: if your Mac is hot and usernoted is at the top of Activity Monitor's CPU tab, don't start by deleting notification databases the internet tells you about — half of them are empty decoys. Start with /usr/bin/log show --predicate 'process == "usernoted"' --last 5m --info and let the daemon tell you, in its own words, who is wasting its life.

Epilogue: the underlying infinite loop — Calendar.nextDate(matching:) hanging forever on unsatisfiable components — is Apple's bug; no app data should be able to do that to a system daemon. Reported via Feedback Assistant. The re-engagement notifications that never gave up? Those are on the app. There's something poetic about an app whose parting gift, after being ignored, was to quietly cook the machine it lived on.


Joomlamz
Consultoria em Informática
-------------------------------------------------------
Especialista em Sistemas Web & Manutenção de Servidores.
A desenvolver o novo AplPortal com suporte a PHP 8.
Precisa de ajuda profissional? Contacte-me.

Tags: