• Keine Ergebnisse gefunden

Pointer Races 7

7.3 Detecting ABAs

So far we have introduced restrictions, namely pointer race freedom and elision support, to rule out cases where our idea of eliding memory reuse does not work, that is, breaks similarity or the SMR behavior inclusion. If those restrictions were strong enough to carry out our development, then we could remove any reuse from a computation and get a similar one where no memory is reused. That the resulting computation does not reuse memory means, intuitively, that it is executed under garbage collection. As shown in the literature [Michael and Scott 1996], the ABA problem is a subtle bug caused by manual memory management which is prevented by garbage collection. So, eliding all reuses jeopardizes soundness of the analysis—it could miss ABAs which result in a safety violation. With this observation, we elide all reuses except for one address per computation. This way we analyze a semantics that is close to garbage collection, can detect ABA problems, and is much simpler than fullO⟦P⟧AdrAdr.

The semantics that we suggest to analyze isO⟦P⟧oneAdr ≔ ⋃𝑎∈AdrO⟦P⟧{Adr𝑎}. It is the set of all computations that reuse at most a single address. A single address suffices to detect the ABA problem. The ABA problem manifests as an assumption of the formassume𝑝 =𝑞where the addresses held by𝑝and𝑞coincide but stem from different allocations. That is, one of the pointers has received its address, the address was freed and then reallocated, before the pointer is used in the assumption. Note that this implies that for an assumption to be ABA one of the involved pointers must be invalid. Pointer race freedom does not forbid this. Nor do we want to forbid such assumptions. In fact, most programs using hazard pointers contain ABAs. They are written in a way that ensures that the ABA isharmless.

Example 7.16 (ABAs in Michael&Scott’s queue using hazard pointers).Consider the following code, repeated from Michael&Scott’s queue from Figure 2.13:

326 Node* head = Head;

327 protect0(head);

328 if (head != Head) continue;

In Line 326 the value of the shared pointerHeadis read into the local pointerhead. Then, a hazard pointer is used in Line 327 to protectheadfrom being freed. In between reading and protectinghead, its address could have been deleted, reused, and reentered the queue. That is,

Section 7.3 Detecting ABAs 73

when executing Line 328 the pointersHeadandheadcan coincide although theheadpointer stems from an earlier allocation. This scenario is an ABA. Nevertheless, the queue’s correctness is not affected by it. The ABA prone assumption is only used to guarantee that the address protected in Line 327 is indeed protected after Line 328. With respect to the SMR automatonOHP, the assumption guarantees that the protection was issued before a retirement (after the latest reallocation) so thatOHP is guaranteed to be in𝐿10and thus prevents future retirements from freeing the protected memory. The ABA does not void this guarantee, it is harmless.

The above example shows that non-blocking data structures may perform ABAs which do not affect their correctness. To soundly verify such algorithms, our approach is to detect every ABA and decide whether it is harmless indeed. If so, our verification is sound. Otherwise, we report to the programmer that the implementation suffers from a harmful ABA problem.

A discussion of how to detect ABAs is in order. Let𝜏 ∈O⟦P⟧AdrAdr and𝜎 ∈O⟦P⟧{Adr𝑎}be similar computations. Intuitively,𝜎is a computation which elides the reuses from𝜏 except for address𝑎. Address𝑎can be used in𝜎in exactly the same way as it is used in𝜏. Letactbe an ABA prone assumption of the formact=⟨𝑡 ,assume𝑝=𝑞,∅⟩. Assumeactis enabled after𝜏. To detect this ABA underO⟦P⟧{Adr𝑎}we needactto be enabled after𝜎. We seek to have𝜎 .act∈O⟦P⟧{Adr𝑎}. This is not guaranteed. Sinceactis an ABA it involves at least one invalid pointer, say𝑝. Computation similarity does not guarantee that𝑝has the same valuation in both𝜏and𝜎. However, if𝑝points to𝑎in𝜏, then it does so in𝜎because𝑎is (re)used in𝜎in the same way as in𝜏. Thus, we end up with𝑚𝜏(𝑝)=𝑚𝜎(𝑝)although𝑝is invalid. In order to guarantee this, we introduce anaddress alignmentrelation which precisely tracks how the reusable address𝑎is used.

Definition 7.17 (Address Alignment).Computations𝜏 and𝜎are𝑎-aligned,𝜏 ≼𝑎𝜎, if:

∀𝑝∈PVar. 𝑚𝜏(𝑝)=𝑎 ⟺ 𝑚𝜎(𝑝)=𝑎

and ∀𝑏∈𝑚𝜏(valid𝜏). 𝑚𝜏(𝑏 .next)=𝑎 ⟺ 𝑚𝜎(𝑏 .next)=𝑎 and 𝑎∈fresh𝜏freed𝜏 ⟺ 𝑎∈fresh𝜎freed𝜎

and FO(𝜏 , 𝑎)⊆FO(𝜎 , 𝑎)

and 𝑎∈retired𝜏 ⟺ 𝑎∈retired𝜎 .

The first line in this definition states that the same pointer variables in𝜏 and𝜎 are pointing to𝑎. Similarly, the second line states this for the pointer selectors of valid addresses. We have to exclude the invalid addresses here because𝜏 and𝜎 may differ on the in-use addresses due to eliding reuse. The third line states that𝑎can be allocated in𝜏 iff it can be allocated in𝜎. The fourth line states that the SMR automaton allows for more behavior on𝑎in𝜎than in𝜏. These properties combined guarantee that𝜎 can mimic actions of𝜏 involving𝑎no matter if invalid pointers are used. The last line requires that𝑎is retired in𝜏 iff it is retired in𝜎. This property makes double retires performed after𝜏 visible in the mimicking𝜎.

74 Chapter 7 Pointer Races

The address alignment lets us detect ABAs inO⟦P⟧oneAdr. Intuitively, we can only detectfirst ABAs because we allow for only a single address to be reused. Subsequent ABAs on different addresses cannot be detected. To detect ABA sequences of arbitrary length, an arbitrary number of reusable addresses is required. To avoid this, i.e., to avoid an analysis of fullO⟦P⟧AdrAdr, we formalize the idea ofharmless ABAsfrom before. We say that an ABA is harmless if executing it leads to a system state which can be explored (by another computation) without performing an ABA. That the system state can be explored without performing an ABA means that every ABA is also a first ABA. Thus, any sequence of ABAs is explored by considering only first ABAs.

Note that this definition is independent of the actual correctness notion.

Definition 7.18 (Harmful ABA).The semanticsO⟦P⟧oneAdris free from harmful ABAs if:

∀𝜎𝑎.act∈O⟦P⟧{Adr𝑎}∀𝜎𝑏 ∈O⟦P⟧{Adr𝑏}∃𝜎𝑏 ∈O⟦P⟧{Adr𝑏}.

𝜎𝑎∼𝜎𝑏act=⟨•,assume•,•⟩ ⟹ 𝜎𝑎.act∼𝜎𝑏 ∧ 𝜎𝑏𝑏𝜎𝑏 ∧ 𝜎𝑎.act⋖𝜎𝑏 . To understand how the definition implements our intuition, consider𝜏 .act∈O⟦P⟧AdrAdr whereact performs an ABA on address𝑎. Our goal is to mimic𝜏 .act inO⟦P⟧{Adr𝑏}, that is, we want to mimic the ABA without reusing address𝑎(for instance, to detect subsequent ABAs on address𝑏).

Assume we are given𝜎𝑏 ∈O⟦P⟧{Adr𝑏}which is similar and𝑏-aligned to𝜏. This does not guarantee thatactcan be mimicked after𝜎𝑏; the ABA may not be enabled because it involves invalid pointers the valuation of which differs in𝜏 and𝜎𝑏. However, we can construct a computation𝜎𝑎 which is similar and𝑎-aligned to𝜏. After𝜎𝑎 the ABA is enabled, i.e.,𝜎𝑎.act∈O⟦P⟧{Adr𝑎}. For those two computations𝜎𝑎.actand𝜎𝑏we invoke the above definition. It yields another com-putation𝜎

𝑏 ∈O⟦P⟧{Adr𝑏} which, intuitively, coincides with𝜎𝑏 but where the ABA has already been executed. Put differently,𝜎𝑏 is a computation which mimics the execution ofactafter𝜎𝑏 althoughactis not enabled.

Example 7.19 (Continued).Consider the computation𝜏 .actof Michael&Scott’s queue with:

𝜏 =𝜏6.⟨𝑡 ,head∶=Head,[head↦𝑎]⟩. 𝜏7.free(𝑎). 𝜏8.

⟨𝑡 ,inprotect0(head),∅⟩.⟨𝑡 ,reprotect0,∅⟩

and act=⟨𝑡 ,assume head=Head,∅⟩.

This computation resembles a thread𝑡 executing Lines 326 to 328 while an interferer frees address𝑎referenced byhead, reallocates it, and makes it theHeadof the queue again; we assume that𝜏6, 𝜏7, 𝜏8consist of the interferer’s actions the precise form of which does not matter here.

Theassumeinactresembles the conditional from Line 328 and states that the condition evaluates totrue. That is,actis a potential ABA on address𝑎.

Reusing address𝑎allows us to mimic𝜏 with an𝑎-aligned computation𝜎𝑎 ∈O⟦P⟧{Adr𝑎}. The ABA prone actionactis guaranteed to be enabled after𝜎𝑎, so𝜎𝑎.actmimics𝜏 .act. Reusing another

Section 7.3 Detecting ABAs 75

address𝑏 yields a𝑏-aligned𝜎𝑏 ∈ O⟦P⟧{Adr𝑏} mimicking𝜏. After𝜎𝑏,act may not be enabled.

The reason for this is that𝜎𝑏 elides allocations of𝑎to avoid it being reused. The interferer’s reallocation of𝑎(in𝜏8) forces𝜎𝑏 to elide its previous allocation. Hence, thread𝑡’sheaddoes not point to𝑎whileHeadstill does. The ABA proneactis not enabled after𝜎𝑏.

To see that the above ABA is harmless, consider the following rescheduling of the actions in𝜏: 𝜏=𝜏6. 𝜏7.free(𝑎). 𝜏8.⟨𝑡 ,head∶=Head,[head↦𝑎]⟩.

⟨𝑡 ,inprotect0(head),∅⟩.⟨𝑡 ,reprotect0,∅⟩.

Here, thread𝑡reads the latest version ofHead. This gives rise to a computation𝜎

𝑏 ∈O⟦P⟧{Adr𝑏} mimicking𝜏. Unlike𝜎𝑏, however,𝜎𝑏 can executeactsince the later read ofheadis not affected by the elision of reallocations. Finally,𝜎𝑏.actmimics𝜏 .act. Requiring the existence of such a𝜎𝑏 guarantees that an analysis cansee pastABAs on address𝑎, although𝑎is not reused.

A key aspect of the above definition is that checking for harmful ABAs can be done in the simpler semanticsO⟦P⟧oneAdr. Altogether, this means that we can rely onO⟦P⟧oneAdr for both the actual analysis and a soundness (absence of harmful ABAs) check. Our experiments show that the above definition is practical. There were no harmful ABAs in the benchmarks we considered.