Haiku BWindow View Transactions: Batch Drawing Without Misusing the Lock
Understand OpenViewTransaction and CommitViewTransaction, their locking contract, and how batching differs from disabling updates or synchronizing app_server.
Haiku’s Interface Kit sends drawing instructions from an application’s BView objects to the separate Application Server. When an operation produces many related drawing calls, sending each one independently adds avoidable message traffic and can expose intermediate work earlier than intended. BWindow::OpenViewTransaction() and CommitViewTransaction() provide a way to bracket a batch of drawing instructions so they are submitted together.
The word “transaction” can be misleading if read as a database guarantee. It is a drawing-command batching mechanism with a strict BWindow locking contract. It does not make arbitrary application-model changes atomic, promise a hardware-level presentation fence, or replace the window’s normal update lifecycle. Understanding the boundary prevents both flicker and deadlocks.
The window lock serializes a transaction
The window must be locked before opening a view transaction and must remain locked until after committing it. A window looper serializes its own message processing; the lock prevents another thread from concurrently manipulating the same window’s Interface Kit state during the bracketed work. Keep the critical section short and ensure every successful open is paired with one commit, including on error and early-return paths.
A simplified C++ pattern is:
if (!window->Lock())
return;
window->OpenViewTransaction();
DrawRelatedUpdates();
window->CommitViewTransaction();
window->Unlock();
This sketch assumes the window is still valid and that DrawRelatedUpdates() cannot throw or return early. Production code should use an RAII guard or a single cleanup path so the lock and transaction are not accidentally left open. Follow the API’s exact object-lifetime and error-handling conventions for the Haiku version you target.
Batching is not the same as disabling updates
DisableUpdates() and EnableUpdates() control automatic window updates, suppressing updates while a larger drawing operation is prepared. A view transaction instead brackets a batch of drawing instructions sent to the Application Server. The tools overlap in some UI workflows but solve different problems. Disabling updates does not, by itself, prove that a large set of individual commands was transported as one transaction; batching drawing commands does not necessarily suppress every update event caused by unrelated state changes.
Choose the narrowest mechanism that expresses the intent. If you are changing many server-side drawing properties and want them grouped, use a view transaction. If the window must not repaint an intermediate state while a coordinated update is prepared, consider the update controls documented for BWindow, while ensuring they are reliably re-enabled. Avoid nesting or combining mechanisms without a test that demonstrates why both are needed.
Flush and Sync do not replace Commit
Ordinary drawing instructions are buffered on the connection to the Application Server. Flush() empties the buffer and submits it; Sync() flushes and waits until the server has executed the submitted work. While a view transaction is open, the API documentation says Flush() invocations are ignored. That is intentional: the transaction owns the batch boundary, and code should commit it rather than trying to flush around it.
CommitViewTransaction() establishes completion of the application’s bracketed batch operation, but do not infer undocumented display guarantees from that name. If a later operation truly requires confirmation that the server has executed drawing already sent, use the appropriate synchronization API after the transaction has been committed and after checking its documented scope. Excessive synchronization serializes application and server progress and can erase the performance advantage of batching.
Keep model changes and rendering changes distinct
A drawing transaction does not roll back changes to application data. If model updates happen midway through a batch and a failure path exits before the matching redraw, the model and screen can still disagree. Prefer preparing a complete state snapshot, then applying the corresponding view updates inside the short locked section. If the model belongs to another thread, send an immutable update to the window’s message loop rather than holding a model mutex while waiting for the window lock.
Think through lock order explicitly. A worker thread that locks a data structure and then blocks acquiring a window lock can deadlock if the window thread is already locked and attempts to read that data structure. Message passing avoids much of this coupling: the worker computes, posts a result, and the window looper applies and draws it using its own serialized state.
Do not hold the window locked during expensive work
File access, network I/O, long calculations, and waits on worker threads do not belong inside a view transaction. While a BWindow remains locked, its looper cannot make normal progress through message handling. A long transaction therefore delays input, close requests, invalidations, and other work directed at the window. Compute drawing data before acquiring the lock, then perform only the brief sequence of view operations needed to publish it.
Also avoid opening a transaction from a code path that already holds the window lock unless its ownership is explicit and the API usage remains balanced. The transaction bracket and lock have independent begin/end operations; balanced locking does not automatically imply a balanced transaction.
A batching test plan
Create a reproducible view that draws a grid or graph with enough primitives to expose partial submission. Record the command count and elapsed time for the unbatched and batched paths. Confirm the window lock is acquired before open, remains held through commit, and is released on all paths. Inject a controlled early failure before drawing, during drawing, and immediately before commit to verify cleanup behavior.
Then test functional behavior under normal events: resize, hide/show, move, expose after occlusion, and rapid repeated updates. The view’s final pixels should match the model regardless of whether an intermediate message arrived while a batch was being prepared. Watch for a frozen title bar or unresponsive close action; those are signs that the application held the window lock too long, not proof that the rendering operation is slow.
Instrument calls to OpenViewTransaction, CommitViewTransaction, Flush, Sync, and lock/unlock in debug builds. A transaction left open can make flushes appear ineffective; a commit without the required lock violates the API contract; a missing unlock stalls the looper. These logs provide a direct diagnosis before changing redraw logic.
The Haiku Book’s BWindow reference is the primary contract for these methods. Use it to verify requirements against the SDK you build with, especially if targeting an older Haiku release or BeOS-compatible environment. Transactions are a focused batching tool; the most maintainable drawing code still derives visible output from application state and uses BView::Invalidate() to request the right updates.
Related:
- Haiku BView Invalidation: Reconstructing Dirty Regions Without Flicker
- Haiku Menus in the Interface Kit: Targets, Shortcuts, and Dynamic Items
Sources: