Class: Gitlab::BackgroundTask

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/background_task.rb

Overview

Used to run small workloads concurrently to other threads in the current process. This may be necessary when accessing process state, which cannot be done via Sidekiq jobs.

Since the given task is put on its own thread, use instances sparingly and only for fast computations since they will compete with other threads such as Puma or Sidekiq workers for CPU time and memory.

Good examples:

  • Polling and updating process counters

  • Observing process or thread state

  • Enforcing process limits at the application level

Bad examples:

  • Running database queries

  • Running CPU bound work loads

As a guideline, aim to yield frequently if tasks execute logic in loops by making each iteration cheap. If life-cycle callbacks like start and stop aren’t necessary and the task does not loop, consider just using Thread.new.

rubocop: disable Gitlab/NamespacedClass

Constant Summary collapse

AlreadyStartedError =
Class.new(StandardError)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(task, **options) ⇒ BackgroundTask

Possible options:

  • name [String] used to identify the task in thread listings and logs (defaults to ‘background_task’)

  • synchronous [Boolean] if true, turns ‘start` into a blocking call



38
39
40
41
42
43
44
45
# File 'lib/gitlab/background_task.rb', line 38

def initialize(task, **options)
  @task = task
  @synchronous = options[:synchronous]
  @name = options[:name] || self.class.name.demodulize.underscore
  # We use a monitor, not a Mutex, because monitors allow for re-entrant locking.
  @mutex = ::Monitor.new
  @state = :idle
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



29
30
31
# File 'lib/gitlab/background_task.rb', line 29

def name
  @name
end

Instance Method Details

#running?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/gitlab/background_task.rb', line 31

def running?
  @state == :running
end

#startObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/gitlab/background_task.rb', line 47

def start
  @mutex.synchronize do
    raise AlreadyStartedError, "background task #{name} already running on #{@thread}" if running?

    start_task = @task.respond_to?(:start) ? @task.start : true

    if start_task
      @state = :running

      at_exit { stop }

      @thread = Thread.new do
        Thread.current.name = name
        @task.call
      end

      @thread.join if @synchronous
    end
  end

  self
end

#stopObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/gitlab/background_task.rb', line 70

def stop
  @mutex.synchronize do
    break unless running?

    if @thread
      # If thread is not in a stopped state, interrupt it because it may be sleeping.
      # This is so we process a stop signal ASAP.
      @thread.wakeup if @thread.alive?
      begin
        # Propagate stop event if supported.
        @task.stop if @task.respond_to?(:stop)

        # join will rethrow any error raised on the background thread
        @thread.join unless Thread.current == @thread
      rescue Exception => ex # rubocop:disable Lint/RescueException
        Gitlab::ErrorTracking.track_exception(ex, extra: { reported_by: name })
      end
      @thread = nil
    end

    @state = :stopped
  end
end