require 'active_job/logging'

# TODO: remove this when the following issue is resolved:
#       https://github.com/rails/rails/issues/21036

# Unsubscribe all Active Job notifications
ActiveSupport::Notifications.unsubscribe 'perform_start.active_job'
ActiveSupport::Notifications.unsubscribe 'perform.active_job'
ActiveSupport::Notifications.unsubscribe 'enqueue_at.active_job'
ActiveSupport::Notifications.unsubscribe 'enqueue.active_job'

module ActiveJob
  module Logging
    class LogSubscriber
      # Remove all public methods so that we can use the class
      # as the base class for our two new subscriber classes.
      remove_method :enqueue
      remove_method :enqueue_at
      remove_method :perform_start
      remove_method :perform
    end

    class EnqueueSubscriber < LogSubscriber
      def enqueue(event)
        info do
          job = event.payload[:job]
          "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
        end
      end

      def enqueue_at(event)
        info do
          job = event.payload[:job]
          "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
        end
      end
    end

    class ExecutionSubscriber < LogSubscriber
      def perform_start(event)
        info do
          job = event.payload[:job]
          "Performing #{job.class.name} from #{queue_name(event)}" + args_info(job)
        end
      end

      def perform(event)
        info do
          job = event.payload[:job]
          "Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2)}ms"
        end
      end
    end
  end
end

# Suubscribe to Active Job notifications
ActiveJob::Logging::EnqueueSubscriber.attach_to :active_job
ActiveJob::Logging::ExecutionSubscriber.attach_to :active_job

Fix memory leak when queuing jobs inside a job
Fix memory leak when queuing jobs inside a job

Active Job uses a single log subscriber class[1] for both perform and
enqueue events. The consequence of this is that any queue events that
happen inside a perform event are treated as child events[2] and so get
added to the event object for the perform event.

This causes us a problem when queuing the individual emails inside a
background job as it means that even though we are using `find_each`
to batch the record loading we eventually end up with 100,000+ records
loaded into memory as part of the event object.

As a temporary fix we can split the log subscriber into two classes -
one for perform events and one for enqueue events that prevents the
enqueue events being treated as child events. It is however possible
that this behaviour is intentional and desired so we should start
considering other approaches for sending bulk email responses.

I've create rails/rails#21036 to track this issue.

[1]: https://github.com/rails/rails/blob/v4.2.3/activejob/lib/active_job/logging.rb#L53-L103
[2]: https://github.com/rails/rails/blob/v4.2.3/activesupport/lib/active_support/subscriber.rb#L88
