ThreadStatic is applied to an instance field
ID |
vbnet.correctness.thread_static_instance_field |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Concurrency |
Language |
VB.NET |
Description
Reports a field declaration carrying <ThreadStatic> (or <ThreadStaticAttribute>) that is not
declared Shared. Both the plain and the initialised declaration forms are reported, under any
access modifier. A Shared field with the same attribute is the supported use and is not
reported.
Rationale
<ThreadStatic> has meaning only for a shared field: it asks the runtime to give each thread
its own copy of the single storage location that backs that field. An instance field already has
one storage location per object, so there is nothing for the attribute to partition and the
runtime ignores it outright - no error, no warning, no thread-local behaviour. The field keeps
exactly the semantics it would have had if the attribute were deleted, which is why the mistake
survives review: the declaration reads as thread-local and behaves as ordinary shared-by-object
state. Code written on the strength of the attribute is then wrong in the way that is hardest to
reproduce. A tracer that keeps its nesting depth in an attributed instance field, reached through
an object several threads hold a reference to, has every thread incrementing and resetting the
same counter; the depth is plausible under a single request and drifts under load. Because the
instance is often long-lived and the interleaving is timing-dependent, the symptom appears as
occasional corrupt output rather than as a failure that points at the declaration.
The following code illustrates the pattern detected by this rule:
Public Class RequestTracer
' FLAGGED: ThreadStatic is applied to an instance field
<ThreadStatic>
Private currentDepth As Integer
' FLAGGED: ThreadStatic is applied to an instance field
<ThreadStatic>
Private Protected activeScope As String = "root"
' FLAGGED: ThreadStatic is applied to an instance field
<ThreadStaticAttribute>
Friend correlationId As Guid
Public Sub Enter(scope As String)
currentDepth += 1
activeScope = scope
End Sub
Remediation
Decide which of the two the field is meant to be. If it really is per-thread state, add Shared
so the attribute takes effect - and remember the field is then one value per thread for the whole
process, initialised to the type default on each new thread, because a field initialiser on a
<ThreadStatic> field runs only on the thread that first touches the class. If the field is
genuinely per-object state, delete the attribute; where the intent was per-thread state that
still needs to be reachable per instance, hold a ThreadLocal(Of T) in an ordinary instance
field instead, which keeps the storage tied to the object and gives each thread its own value
with a proper initialiser.
' Before: the attribute is ignored; every thread shares this object's counter
Public Class RequestTracer
<ThreadStatic>
Private currentDepth As Integer
End Class
' After: per-thread state, kept per instance and properly initialised
Public Class RequestTracer
Private ReadOnly currentDepth As New ThreadLocal(Of Integer)(Function() 0)
End Class