Skip to content

Application Decisions

Avoid Class-Level State in Concurrent Environments

Class variables and class-level state introduce significant risk in multi-threaded or multi-request environments. Because class variables are shared across all instances of a class, modifying them affects every request, every thread, and every user. This frequently leads to race conditions, unpredictable behavior, and cross-request contamination.

Consider the following example:

ruby
def update_without_logging(resource_id)
  Audit.disable_logging

  @resource = Audit.find(resource_id)
  @resource.update(some_field: 'some value')

  Audit.enable_logging
end

Why this is dangerous

Audit.disable_logging changes a shared class-level flag.
If multiple requests are being processed concurrently:

  • Request A disables logging
  • Request B begins at the same time → logging is now disabled for B
  • Request A finishes and re-enables logging
  • Request B may still assume logging should be disabled
  • Or another request may unintentionally run with logging disabled

This pattern results in:

  • Non-deterministic logging behavior
  • Cross-request side effects
  • Race conditions
  • Bugs that are extremely difficult to reproduce

This happens because class variables:

  • are global to the class
  • live for the lifetime of the process
  • are not garbage-collected
  • are shared between all threads and all requests

Correct Approach: Use Instance-Level State

Instead of modifying shared global state, state should always be scoped to the specific object or request being processed.

For example:

ruby
def update_without_logging(resource_id)
  @resource = Audit.find(resource_id)
  @resource.disable_logging
  @resource.update(some_field: 'some value')
end

Here:

  • Logging behavior applies only to the specific resource instance
  • No other request is affected
  • No shared state is involved
  • No race conditions are possible

Summary

To avoid hard-to-debug concurrency issues:

  • Never use class variables or class-level flags to control request-specific behavior
  • Always prefer instance variables or request-scoped state
  • Avoid global switches or mutable class-level configuration
  • Design for concurrency by default

Following these principles ensures your application remains safe, predictable, and correct under load.