Published on

Object Lifecycle & Memory Discipline di Embedded

Authors

πŸ“˜ Artikel 3: Object Lifecycle & Memory Discipline di Embedded

Posisi: Artikel 3 dari 8 Domain Keputusan: Memory & Allocation Policy Status Lock: πŸ”’ Allocation & Lifecycle Freeze Entry Context: IndustrialNode/IndustrialNode.ino



1. Problem Reality

Setelah Artikel 2:

  • Tidak ada global mutable.
  • Dependency sudah eksplisit.
  • State punya owner.

Firmware terlihat rapi.

Namun setelah 2–4 minggu deployment, gejala mulai muncul:

  • Reset tanpa sebab jelas.
  • TLS handshake gagal sporadis.
  • OTA kadang gagal.
  • Heap minimum terus menurun.
  • Watchdog reset saat traffic tinggi.

Kode terlihat bersih.

Masalahnya bukan dependency lagi.

Masalahnya:

Lifecycle object dan penggunaan heap tidak disiplin.

Firmware embedded tidak boleh bergantung pada β€œruntime flexibility” seperti sistem desktop.

ESP32 memiliki:

  • Heap terbatas
  • TLS yang memakan memori besar
  • WiFi stack internal
  • FreeRTOS task internal

Heap bukan milik kita sepenuhnya.


2. Root Cause Analysis

Masalah memory discipline di firmware Arduino ESP32 biasanya berasal dari 4 pola berikut.


2.1 Dynamic Allocation Tersembunyi

Contoh klasik:

String payload = "{ \"temp\": " + String(temp) + " }";
client.publish("node/data", payload.c_str());

Yang terjadi:

  • Allocation heap
  • Reallocation saat concat
  • Copy internal buffer

Setiap loop publish β†’ heap churn.

Fragmentasi tidak langsung terasa. Tetapi setelah minggu ke-3, TLS gagal.


2.2 Object Dibuat di Dalam Loop

void loop() {
    TelemetryPacket pkt;
    pkt.build();
    comm.publish(pkt);
}

Jika TelemetryPacket memiliki buffer internal besar:

  • Stack pressure meningkat.
  • Lifecycle implicit.
  • Sulit diaudit.

Jika ada allocation di constructor, makin berbahaya.


2.3 Growable Container Tanpa Batas

std::vector<Telemetry> buffer;
buffer.push_back(data);

Tanpa reserve() dan tanpa batas:

  • Reallocation berkali-kali
  • Fragmentasi
  • Latency spike

Embedded β‰  server.


2.4 Destructor Mengandung Logic Berat

~CommManager() {
    mqtt.disconnect();
}

Destructor tidak eksplisit dipanggil oleh engineer.

Dalam embedded tanpa exception control yang ketat:

  • Destructor berat tidak deterministik.
  • Bisa memicu blocking tak terduga.

πŸ”Ž Visualisasi Fragmentasi Heap

Image

Image

Fragmentasi bukan berarti heap habis. Masalahnya adalah:

  • Free memory terpecah kecil-kecil.
  • TLS membutuhkan blok besar kontigu.
  • Allocation gagal walau free memory terlihat cukup.

Ini silent killer.


3. Design Principle (Rule yang Dikunci)

Artikel ini mengunci baseline memory final.


πŸ”’ Rule 1 β€” Static-First Allocation

Semua object utama firmware harus:

  • Dideklarasikan statik di IndustrialNode.ino
  • Lifetime = lifetime firmware

Contoh:

RelayDriver relay(5);
ActuatorService actuator(relay);
ControlApp app(actuator);

Tidak dibuat di dalam loop().


πŸ”’ Rule 2 β€” Heap Restricted Zone

Heap hanya boleh digunakan untuk:

  • TLS internal
  • MQTT internal buffer
  • OTA buffer terbatas

Selain itu β†’ dilarang.


πŸ”’ Rule 3 β€” No Allocation in Control Loop

Dilarang di app.run() atau loop():

  • new
  • delete
  • String
  • container growable

Control loop harus deterministic.


πŸ”’ Rule 4 β€” No Allocation in ISR

ISR hanya boleh:

  • Set flag
  • Notify task
  • Push ke queue fixed-size

Tidak boleh allocate.


πŸ”’ Rule 5 β€” Destructor Harus Trivial

Destructor tidak boleh:

  • Blocking
  • Publish
  • Logging berat
  • Free resource kompleks

Lifecycle harus eksplisit.


Setelah Artikel 3:

  • ❌ Tidak boleh introduce dynamic allocation runtime
  • ❌ Tidak boleh String di hot path
  • ❌ Tidak boleh container growable tanpa batas

Memory baseline terkunci.


4. Implementation Pattern (ESP32 Arduino Context)


4.1 Static Composition Root

Di IndustrialNode.ino:

#include "drv_RelayDriver.h"
#include "svc_ActuatorService.h"
#include "app_ControlApp.h"

RelayDriver relay(5);
ActuatorService actuator(relay);
ControlApp app(actuator);

void setup() {
    app.init();
}

void loop() {
    app.run();
}

Semua object utama:

  • Dibuat sekali
  • Tidak pernah dihancurkan
  • Lifetime eksplisit

4.2 Fixed-Size Buffer Pattern

Daripada:

std::vector<Telemetry> buffer;

Gunakan:

#define TELEMETRY_BUFFER_SIZE 10

Telemetry telemetryBuffer[TELEMETRY_BUFFER_SIZE];
uint8_t head = 0;
uint8_t count = 0;

Bounded. Predictable. Audit-able.


5. Constraint & Embedded Impact

Disiplin memory bukan soal β€œgaya C++ lama”. Ini soal survival di lingkungan dengan batas keras.


5.1 RAM Impact

ESP32 (Arduino core) berbagi RAM untuk:

  • WiFi stack
  • TCP/IP
  • TLS (mbedTLS)
  • MQTT client
  • FreeRTOS task
  • Heap user

Jika firmware menggunakan dynamic allocation liar:

  • Heap minimum turun perlahan.
  • Fragmentasi meningkat.
  • Allocation besar (TLS handshake) gagal.

Static-first allocation:

  • Menghilangkan churn heap.
  • Mengurangi fragmentasi.
  • Membuat penggunaan RAM predictable.

5.2 Flash Impact

Menghindari template berat dan container kompleks:

  • Mengurangi instantiation code.
  • Mengurangi binary size.
  • Mengurangi risiko OTA gagal karena ukuran firmware terlalu besar.

Flash bukan infinite resource.


5.3 Stack Impact

Object besar di stack:

void run() {
    LargePacket pkt;
}

Jika LargePacket memiliki buffer 512 byte:

  • Stack task bisa overflow.
  • Crash sporadis sulit dideteksi.

Solusi:

  • Gunakan static buffer.
  • Hindari object besar di stack lokal.

5.4 Determinism Impact

Dynamic allocation:

  • Waktu eksekusi tidak konstan.
  • Reallocation latency tidak terprediksi.
  • TLS handshake bisa memicu spike.

Control loop harus:

  • Konstan
  • Ringan
  • Tidak tergantung heap

Memory discipline = determinism.


πŸ”Ž Visualisasi Stack vs Heap Risk

Image

Image

Image

Image

Jika stack overflow atau heap terfragmentasi, hasilnya bukan warning β€” tapi reset.


6. Failure Scenario

Memory problem jarang langsung meledak. Ia perlahan merusak stabilitas.


Scenario 1 β€” Fragmentasi Heap Mingguan

Kondisi:

  • Publish tiap 5 detik.
  • Gunakan String.
  • TLS aktif.

Minggu 1: Stabil. Minggu 2: Heap minimum turun. Minggu 3: TLS handshake gagal. Minggu 4: Device reset.

Root cause:

  • Reallocation berulang.
  • Fragmentasi kontigu hilang.

Dengan static-first policy:

  • Tidak ada churn.
  • Heap stabil.

Scenario 2 β€” Burst Telemetry Buffer Tanpa Batas

WiFi down 30 menit.

Firmware menggunakan:

std::vector<Telemetry> backlog;

Backlog tumbuh terus.

Saat reconnect:

  • Publish burst.
  • Allocation besar.
  • Crash.

Dengan fixed-size ring buffer:

  • Data lama di-drop terkontrol.
  • Tidak ada spike allocation.

Scenario 3 β€” Object Besar di Stack

Callback parsing JSON membuat object lokal besar.

Stack default task tidak cukup.

Hasil:

  • Crash sporadis.
  • Sulit reproduksi.

Dengan static buffer:

  • Stack stabil.
  • Predictable.

Scenario 4 β€” Destructor Logic Berat

Object lokal memiliki destructor yang memanggil disconnect().

Ketika keluar scope:

  • Blocking terjadi.
  • Latency spike.
  • Watchdog reset.

Destructor harus trivial.


7. Anti-Pattern

Daftar merah Artikel 3:

  • ❌ new / delete di runtime loop
  • ❌ String di hot path
  • ❌ std::vector tanpa batas
  • ❌ Object besar di stack
  • ❌ Allocation di ISR
  • ❌ Destructor memanggil network
  • ❌ Library pihak ketiga tanpa audit allocation

Dampak:

  • Fragmentasi
  • Latency spike
  • Reset misterius
  • OTA gagal

8. Freeze Point

Setelah Artikel 3, keputusan berikut dianggap final:

  • Static-first allocation policy.
  • Heap hanya untuk zona terbatas (TLS/MQTT/OTA).
  • Tidak ada dynamic allocation di control loop.
  • Tidak ada String di path kritikal.
  • Tidak ada allocation di ISR.
  • Destructor harus trivial.

Memory baseline tidak boleh berubah di artikel berikutnya.


9. Engineering Checklist

Audit sebelum release:

  • Apakah ada new atau delete?
  • Apakah ada String di loop?
  • Apakah ada container growable tanpa batas?
  • Apakah heap minimum dimonitor?
  • Apakah ada object besar di stack?
  • Apakah ISR allocate?

Jika satu saja β€œya” β†’ melanggar Artikel 3.


10. Summary (5 Bullet Maksimal)

  • Fragmentasi heap adalah silent killer firmware.
  • Static-first allocation membuat sistem predictable.
  • Dynamic allocation merusak determinism.
  • Destructor berat tidak cocok untuk embedded.
  • Memory discipline menentukan uptime jangka panjang.

Catatan Penyusunan Artikel ini disusun sebagai materi edukasi dan referensi umum berdasarkan berbagai sumber pustaka, praktik lapangan, serta bantuan alat penulisan. Pembaca disarankan untuk melakukan verifikasi lanjutan dan penyesuaian sesuai dengan kondisi serta kebutuhan masing-masing sistem.