• Keine Ergebnisse gefunden

Pointer Races 7

7.2 Preserving Similarity

The development in Section 7.1 is idealized. There are cases where the introduced relations do not guarantee that an action can be mimicked. All such cases have in common that they involve invalid pointers. More precisely, (i) the computation similarity may not be strong enough to mimic actions that dereference invalid pointers, and (ii) the SMR behavior inclusion may not be strong enough to mimic calls involving invalid pointers. For each of those cases we give an example and restrict our development. We argue throughout this section that our restrictions are reasonable. Our experiments confirm this. We start with the computation similarity.

Example 7.9 (Continued).Consider the following continuation of𝜏3and𝜎3:

𝜏4 =𝜏3.⟨𝑡 , 𝑞 .next∶=𝑞,[𝑎 .next↦𝑎]⟩.⟨𝑡 , 𝑝 .next∶=𝑝,[𝑎 .next ↦𝑎]⟩

and 𝜎4 =𝜎3.⟨𝑡 , 𝑞 .next∶=𝑞,[𝑎 .next↦𝑎]⟩.⟨𝑡 , 𝑝 .next∶=𝑝,[𝑏 .next ↦𝑏]⟩.

The first appended action updates𝑎 .nextin both computations to𝑎. Since𝑞is valid after both𝜏3 and𝜎3this assignment renders valid𝑎 .next. The second action updates𝑎 .nextin𝜏4. This results in𝑎 .nextbeing invalid after𝜏4because the right-hand side of the assignment is the invalid𝑝. In𝜎4 the second action updates𝑏 .nextwhich is why𝑎 .next remains valid. That is, the valid memories of𝜏4and𝜎4differ. We have executed an action that cannot be mimicked on the valid

memory despite the computations being similar.

Section 7.2 Preserving Similarity 69

The problem in the above example is the dereference of an invalid pointer. The computation similarity does not give any guarantees about the valuation of such pointers. Consequently, it cannot guarantee that an action using invalid pointers can be mimicked. To avoid such problems, we forbid programs to dereference invalid pointers.

The rational behind this is as follows. Recall that an invalid pointer is dangling. That is, the memory it references has been freed. If the memory has been returned to the underlying operating system, then a subsequent dereference is unsafe, i.e., prone to a system crash due to asegfault. Hence, such dereferences should be avoided. The dereference is only safe if the memory is guaranteed to be accessible. To decide this, the invalid pointer needs to be compared with a definitely valid pointer. Such a comparison renders valid the invalid pointer (cf. Definition 6.5).

This means that dereferences of invalid pointers are always unsafe. We let verification fail if unsafe accesses are performed. That performance-critical and non-blocking code is free from unsafe accesses is confirmed by our experiments.

Definition 7.10 (Unsafe Access).A computation𝜏 .⟨𝑡 ,com,up⟩performs an unsafe access ifcomcontains𝑝 .dataor𝑝 .nextwith𝑝∉valid𝜏.

Forbidding unsafe accesses makes the computation similarity strong enough to mimic all desired actions. A discussion of cases where the SMR behavior inclusion cannot be preserved is in order.

We start with an example.

Example 7.11 (Continued).Consider the following continuations of𝜏1, 𝜎1from Example 7.1:

𝜏5 =𝜏1.⟨𝑡 ,inretire(𝑝),∅⟩ with H(𝜏5) = H(𝜏1).inretire(𝑡 , 𝑎) and 𝜎5=𝜎1.⟨𝑡 ,inretire(𝑝),∅⟩ with H(𝜎5) = H(𝜎1).inretire(𝑡 , 𝑏). The SMR behavior of𝜏1 is included in𝜎1, that is,𝜏1 ⋖𝜎1. After𝜏5 a deletion of𝑎is possible because it is retired. After𝜎5a deletion of𝑎is prevented byOBasebecause𝑎has not been retired.

Formally, we havefree(𝑎)∈FO(𝜏5, 𝑎)andfree(𝑎)∉FO(𝜎5, 𝑎). However,𝑎is a valid address because it is referenced by the valid pointer𝑞. That is, the behavior inclusion among𝜏1and𝜎1is

not preserved by the subsequent action.

The above example showcases that calls to the SMR algorithm can break the SMR behavior inclusion. This is the case because an action can emit different events in similar computations.

The event emitted by an SMR call differs only if it involves invalid pointers.

The naive solution would be to prevent using invalid pointers in calls altogether. In practice, this is too strong a requirement. As seen in Chapter 2, a common pattern for protecting an address with hazard pointers is to (i) read a pointer𝑝into a local variable𝑞, (ii) issue a protection

70 Chapter 7 Pointer Races

using𝑞, and (iii) repeat the process if𝑝and𝑞do not coincide.1After reading into𝑞and before protecting𝑞the referenced memory may be freed. Hence, the protection is prone to use invalid pointers. Forbidding such protections would render our theory inapplicable to non-blocking data structures using hazard pointers.

To fight this problem, we forbid only those calls involving invalid pointers which are prone to breakthe SMR behavior inclusion. Intuitively, this is the case if there exists another call which differs only on the invalid pointer arguments and allows for more behavior on the valid addresses than the original call. To regain precision and support more scenarios where invalid pointers are used, we keep unchanged the address the behavior of which is considered.

Definition 7.12 (Racy Call).Computation𝜏 .(𝑡 ,in∶func(𝑟),∅)performs a racy call if the following holds forℎ=H(𝜏)and𝑣 =𝑚𝜏(𝑟):

∃𝑎∃𝑤 . (∀𝑖 .(𝑣𝑖=𝑎∨𝑟𝑖valid𝜏∨𝑟𝑖DExp) ⟹ 𝑣𝑖=𝑤𝑖)

∧ FO(ℎ .in∶func(𝑡 , 𝑤), 𝑎) /⊆FO(ℎ .infunc(𝑡 , 𝑣), 𝑎)

It follows immediately that calls containing only valid pointers are not racy. Using the SMR automaton for HP,OBase×OHP0 ×OHP1 , we deem racy the call toretirefrom Example 7.11—we reject the program and let verification fail. Indeed, requesting the deferred deletion of an invalid pointer might lead to a double free, resulting in a system crash. For that reason,retireis always called using valid pointers in practice. Forprotectcalls one can show that they never race. We have already seen this in Example 7.6. There, a call toprotectwith invalid pointers did not break the SMR behavior inclusion. Instead, the mimicking computation𝜎3could perform strictly more frees than the computation it mimicked𝜏3.

We uniformly refer to the above situations where the usage of an invalid pointer can break the ability to mimic an action as apointer race. It is a race indeed because the usage and the reclamation of a pointer are not properly synchronized.

Definition 7.13 (Pointer Race).A computation𝜏 .actis a pointer race ifactperforms (i) an unsafe access, or (ii) a racy SMR call.

With pointer races we restrict the class of supported programs. The restriction to pointer race free programs is reasonable in that we can handle common non-blocking data structures from the literature as shown in our experiments. Since we want to give the main result of this section in a general fashion that does not rely on the actual SMR automaton used to specify the SMR implementation, we have to restrict the class of supported SMR automata as well.

1For an instantiation of this pattern, consider Lines 326 to 328 of Michael&Scott’s queue from Figure 2.13.

Section 7.2 Preserving Similarity 71

We require that the SMR automaton supports the elision of reused addresses, as done in Exam-ple 7.1. Intuitively, elision is a two-step process the automaton must be insensitive to. First, an address𝑎is swapped with a fresh address𝑏upon an allocation where𝑎should be reused but cannot. In the resulting computation,𝑎is fresh and thus the allocation can be performed without reusing𝑎. The process of swapping𝑎with𝑏must not affect the behavior of the automaton on addresses other than𝑎and𝑏. Second, the automaton must allow for more behavior on the fresh address than on the reused address. This is required to preserve the SMR behavior inclusion because the allocation renders𝑎valid.

Additionally, we require a third property: the SMR automaton behavior on an address must not be influenced by frees of another address. This is needed because computation similarity and SMR behavior inclusion do not guarantee that frees of invalid addresses can be mimicked, as discussed before. Since such frees do not affect the valid memory, there is no need to mimic them.

The SMR automaton has to allow us to simplyskipsuch frees when mimicking a computation.

For a formal definition of our intuition we writeℎ[𝑎/𝑏]to denote the history that is constructed fromℎby swapping every occurrence of𝑎and𝑏. Moreover, we write𝑎∈fresh and mean that address𝑎does not appear in any of the events ofℎ.

Definition 7.14 (Elision Support).SMR automatonO=OBase×OSMRsupports elision of memory reuse if for allℎ, ℎ∈S(OBase)and𝑎, 𝑏, 𝑐∈Adrthe following conditions are met:

(i) 𝑎≠𝑐≠𝑏impliesFOSMR(ℎ, 𝑐)=FOSMR(ℎ[𝑎/𝑏], 𝑐),

(ii) FO(ℎ, 𝑎)⊆FO(ℎ, 𝑎)and𝑏∈fresh impliesFOSMR(ℎ, 𝑏)⊆FOSMR(ℎ, 𝑏), and (iii) 𝑎≠𝑏andℎ .free(𝑎)∈S(O)impliesFOSMR(ℎ, 𝑏)=FOSMR(ℎ .free(𝑎), 𝑏).

Crucially, the above definition is concerned with the SMR-specific partOSMR only. Automa-ton OBase does not satisfy Property (ii) for arbitrary histories ℎ, ℎ. The problem is that aninretire(𝑡 , 𝑏)in historyℎ takesOBase from its initial location𝐿2 to location 𝐿3. In𝐿3 afree(𝑏)is allowed. However, that𝑏is fresh inℎmeans thatOBaseis in𝐿2wherefree(𝑏)is not allowed. So,ℎdoes not include all ofℎ’s behavior. When constructing for𝜏a mimicking compu-tation𝜎 that elides the reuse of𝑏, the problematic scenario occurs only if𝑏is both freed and retired after𝜏. This, in turn, gives rise to a double retire on𝑏. To see this, observe that𝑏must have been retired after being freed as otherwiseOBasewould be in𝐿2rather than𝐿3afterℎ=H(𝜏).

For𝑏to be freed, there must be a preceding retirement according toOBase. Removing the free, we obtain a computation where𝑏remains retired and is thus retired twice. Hence, a check for double retires, which Section 5.2 mandates anyways, yields a lift of Property (ii) to fullO in the relevant situations. It is worth pointing out that we cannot rely on pointer races here. While the SMR automata we use would deem racy a retirement of a freed and thus invalid address, this is not guaranteed in general.

72 Chapter 7 Pointer Races

We found Definition 7.14 practical in that the SMR automata for specifying HP and EBR from Figure 5.4, which we use for our experiments in Section 7.5, support elision.

Proposition 7.15.The SMR automataOBase×OEBRandOBase×OHP0 ×OHP1 from Figure 5.4 as well asOBase×OHP0,1support elision.