Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Sidekiq Web to delete enqueued jobs - doesn't remove lock #14

Open
eldad87 opened this issue Nov 18, 2020 · 10 comments
Open

Using Sidekiq Web to delete enqueued jobs - doesn't remove lock #14

eldad87 opened this issue Nov 18, 2020 · 10 comments

Comments

@eldad87
Copy link

eldad87 commented Nov 18, 2020

Hey,
Using Sidekiq Web to delete enqueued jobs, lock remain active.

Steps to reproduce:

  1. Create a Job with until_and_while_executing
  2. Enqueue job
  3. Dequeue the job using Sidekiq Web
  4. Enqueue the same job again

Expected result: Should be able to enqueue the job
Actual result: I recieve an error Not unique ClassNameJob (Job ID: ce10cf03-d2c4-4369-a4ec-5f50316acf66) (Lock key: activejob_uniqueness:..

@eldadvcita
Copy link

Gemfile:

PATH
  remote: vendor/shipit-engine
  specs:
    shipit-engine (0.32.0)
      active_model_serializers (~> 0.9.3)
      ansi_stream (~> 0.0.6)
      attr_encrypted (~> 3.1.0)
      autoprefixer-rails (~> 6.4.1)
      coffee-rails (~> 5.0)
      explicit-parameters (~> 0.4.0)
      faraday (~> 0.15)
      faraday-http-cache (~> 1.2.2)
      gemoji (~> 2.1)
      jquery-rails (~> 4.1.0)
      lodash-rails (~> 4.6.1)
      octokit (~> 4.15)
      omniauth-github (~> 1.4)
      pubsubstub (~> 0.2.0)
      rails (~> 6.0.0)
      rails-timeago (~> 2.13.0)
      rails_autolink (~> 1.1.6)
      rake
      redis-namespace (~> 1.6.0)
      redis-objects (~> 1.4.3)
      responders (~> 3.0)
      safe_yaml (~> 1.0.4)
      sass-rails (~> 5.0)
      securecompare (~> 1.0.0)
      sprockets-rails (>= 2.3.2)
      state_machines-activerecord (~> 0.6.0)
      validate_url (~> 1.0.0)

GEM
  remote: https://rubygems.org/
  specs:
    actioncable (6.0.3.4)
      actionpack (= 6.0.3.4)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
    actionmailbox (6.0.3.4)
      actionpack (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      mail (>= 2.7.1)
    actionmailer (6.0.3.4)
      actionpack (= 6.0.3.4)
      actionview (= 6.0.3.4)
      activejob (= 6.0.3.4)
      mail (~> 2.5, >= 2.5.4)
      rails-dom-testing (~> 2.0)
    actionpack (6.0.3.4)
      actionview (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      rack (~> 2.0, >= 2.0.8)
      rack-test (>= 0.6.3)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.0, >= 1.2.0)
    actiontext (6.0.3.4)
      actionpack (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      nokogiri (>= 1.8.5)
    actionview (6.0.3.4)
      activesupport (= 6.0.3.4)
      builder (~> 3.1)
      erubi (~> 1.4)
      rails-dom-testing (~> 2.0)
      rails-html-sanitizer (~> 1.1, >= 1.2.0)
    active_model_serializers (0.9.7)
      activemodel (>= 3.2)
      concurrent-ruby (~> 1.0)
    activejob (6.0.3.4)
      activesupport (= 6.0.3.4)
      globalid (>= 0.3.6)
    activejob-uniqueness (0.1.4)
      activejob (>= 4.2, < 7)
      redlock (>= 1.2, < 2)
    activemodel (6.0.3.4)
      activesupport (= 6.0.3.4)
    activerecord (6.0.3.4)
      activemodel (= 6.0.3.4)
      activesupport (= 6.0.3.4)
    activestorage (6.0.3.4)
      actionpack (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      marcel (~> 0.3.1)
    activesupport (6.0.3.4)
      concurrent-ruby (~> 1.0, >= 1.0.2)
      i18n (>= 0.7, < 2)
      minitest (~> 5.1)
      tzinfo (~> 1.1)
      zeitwerk (~> 2.2, >= 2.2.2)
    addressable (2.7.0)
      public_suffix (>= 2.0.2, < 5.0)
    ansi_stream (0.0.6)
    attr_encrypted (3.1.0)
      encryptor (~> 3.0.0)
    autoprefixer-rails (6.4.1.1)
      execjs
    axiom-types (0.1.1)
      descendants_tracker (~> 0.0.4)
      ice_nine (~> 0.11.0)
      thread_safe (~> 0.3, >= 0.3.1)
    bindex (0.8.1)
    bootsnap (1.4.8)
      msgpack (~> 1.0)
    builder (3.2.4)
    byebug (11.1.3)
    capybara (3.33.0)
      addressable
      mini_mime (>= 0.1.3)
      nokogiri (~> 1.8)
      rack (>= 1.6.0)
      rack-test (>= 0.6.3)
      regexp_parser (~> 1.5)
      xpath (~> 3.2)
    childprocess (3.0.0)
    coderay (1.1.3)
    coercible (1.0.0)
      descendants_tracker (~> 0.0.1)
    coffee-rails (5.0.0)
      coffee-script (>= 2.2.0)
      railties (>= 5.2.0)
    coffee-script (2.4.1)
      coffee-script-source
      execjs
    coffee-script-source (1.12.2)
    concurrent-ruby (1.1.7)
    connection_pool (2.2.3)
    crass (1.0.6)
    descendants_tracker (0.0.4)
      thread_safe (~> 0.3, >= 0.3.1)
    encryptor (3.0.0)
    equalizer (0.0.11)
    erubi (1.9.0)
    execjs (2.7.0)
    explicit-parameters (0.4.0)
      actionpack (>= 6.0)
      activemodel (>= 6.0)
      virtus (~> 1.0)
    faraday (0.17.3)
      multipart-post (>= 1.2, < 3)
    faraday-http-cache (1.2.2)
      faraday (~> 0.8)
    ffi (1.13.1)
    gemoji (2.1.0)
    globalid (0.4.2)
      activesupport (>= 4.2.0)
    hashie (4.1.0)
    i18n (1.8.5)
      concurrent-ruby (~> 1.0)
    ice_nine (0.11.2)
    jbuilder (2.10.1)
      activesupport (>= 5.0.0)
    jquery-rails (4.1.1)
      rails-dom-testing (>= 1, < 3)
      railties (>= 4.2.0)
      thor (>= 0.14, < 2.0)
    jwt (2.2.2)
    listen (3.2.1)
      rb-fsevent (~> 0.10, >= 0.10.3)
      rb-inotify (~> 0.9, >= 0.9.10)
    lodash-rails (4.6.1)
      railties (>= 3.1)
    loofah (2.7.0)
      crass (~> 1.0.2)
      nokogiri (>= 1.5.9)
    mail (2.7.1)
      mini_mime (>= 0.1.1)
    marcel (0.3.3)
      mimemagic (~> 0.3.2)
    method_source (1.0.0)
    mimemagic (0.3.5)
    mini_mime (1.0.2)
    mini_portile2 (2.4.0)
    minitest (5.14.2)
    msgpack (1.3.3)
    multi_json (1.15.0)
    multi_xml (0.6.0)
    multipart-post (2.1.1)
    mysql2 (0.5.3)
    nio4r (2.5.4)
    nokogiri (1.10.10)
      mini_portile2 (~> 2.4.0)
    oauth2 (1.4.4)
      faraday (>= 0.8, < 2.0)
      jwt (>= 1.0, < 3.0)
      multi_json (~> 1.3)
      multi_xml (~> 0.5)
      rack (>= 1.2, < 3)
    octokit (4.19.0)
      faraday (>= 0.9)
      sawyer (~> 0.8.0, >= 0.5.3)
    omniauth (1.9.1)
      hashie (>= 3.4.6)
      rack (>= 1.6.2, < 3)
    omniauth-github (1.4.0)
      omniauth (~> 1.5)
      omniauth-oauth2 (>= 1.4.0, < 2.0)
    omniauth-oauth2 (1.7.0)
      oauth2 (~> 1.4)
      omniauth (~> 1.9)
    pry (0.13.1)
      coderay (~> 1.1)
      method_source (~> 1.0)
    pry-byebug (3.9.0)
      byebug (~> 11.0)
      pry (~> 0.13.0)
    public_suffix (4.0.6)
    pubsubstub (0.2.1)
      rack
      redis (~> 4.0)
    puma (4.3.6)
      nio4r (~> 2.0)
    rack (2.2.3)
    rack-proxy (0.6.5)
      rack
    rack-test (1.1.0)
      rack (>= 1.0, < 3)
    rails (6.0.3.4)
      actioncable (= 6.0.3.4)
      actionmailbox (= 6.0.3.4)
      actionmailer (= 6.0.3.4)
      actionpack (= 6.0.3.4)
      actiontext (= 6.0.3.4)
      actionview (= 6.0.3.4)
      activejob (= 6.0.3.4)
      activemodel (= 6.0.3.4)
      activerecord (= 6.0.3.4)
      activestorage (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      bundler (>= 1.3.0)
      railties (= 6.0.3.4)
      sprockets-rails (>= 2.0.0)
    rails-dom-testing (2.0.3)
      activesupport (>= 4.2.0)
      nokogiri (>= 1.6)
    rails-html-sanitizer (1.3.0)
      loofah (~> 2.3)
    rails-timeago (2.13.0)
      actionpack (>= 3.1)
      activesupport (>= 3.1)
    rails_autolink (1.1.6)
      rails (> 3.1)
    railties (6.0.3.4)
      actionpack (= 6.0.3.4)
      activesupport (= 6.0.3.4)
      method_source
      rake (>= 0.8.7)
      thor (>= 0.20.3, < 2.0)
    rake (13.0.1)
    rb-fsevent (0.10.4)
    rb-inotify (0.10.1)
      ffi (~> 1.0)
    redis (4.2.2)
    redis-namespace (1.6.0)
      redis (>= 3.0.4)
    redis-objects (1.4.3)
      redis (~> 4.0)
    redlock (1.2.0)
      redis (>= 3.0.0, < 5.0)
    regexp_parser (1.8.2)
    responders (3.0.1)
      actionpack (>= 5.0)
      railties (>= 5.0)
    rubyzip (2.3.0)
    safe_yaml (1.0.5)
    sass (3.7.4)
      sass-listen (~> 4.0.0)
    sass-listen (4.0.0)
      rb-fsevent (~> 0.9, >= 0.9.4)
      rb-inotify (~> 0.9, >= 0.9.7)
    sass-rails (5.1.0)
      railties (>= 5.2.0)
      sass (~> 3.1)
      sprockets (>= 2.8, < 4.0)
      sprockets-rails (>= 2.0, < 4.0)
      tilt (>= 1.1, < 3)
    sawyer (0.8.2)
      addressable (>= 2.3.5)
      faraday (> 0.8, < 2.0)
    securecompare (1.0.0)
    selenium-webdriver (3.142.7)
      childprocess (>= 0.5, < 4.0)
      rubyzip (>= 1.2.2)
    sidekiq (6.1.2)
      connection_pool (>= 2.2.2)
      rack (~> 2.0)
      redis (>= 4.2.0)
    spring (2.1.1)
    spring-watcher-listen (2.0.1)
      listen (>= 2.7, < 4.0)
      spring (>= 1.2, < 3.0)
    sprockets (3.7.2)
      concurrent-ruby (~> 1.0)
      rack (> 1, < 3)
    sprockets-rails (3.2.2)
      actionpack (>= 4.0)
      activesupport (>= 4.0)
      sprockets (>= 3.0.0)
    state_machines (0.5.0)
    state_machines-activemodel (0.7.1)
      activemodel (>= 4.1)
      state_machines (>= 0.5.0)
    state_machines-activerecord (0.6.0)
      activerecord (>= 4.1)
      state_machines-activemodel (>= 0.5.0)
    thor (1.0.1)
    thread_safe (0.3.6)
    tilt (2.0.10)
    turbolinks (5.2.1)
      turbolinks-source (~> 5.2)
    turbolinks-source (5.2.0)
    tzinfo (1.2.7)
      thread_safe (~> 0.1)
    validate_url (1.0.13)
      activemodel (>= 3.0.0)
      public_suffix
    virtus (1.0.5)
      axiom-types (~> 0.1)
      coercible (~> 1.0)
      descendants_tracker (~> 0.0, >= 0.0.3)
      equalizer (~> 0.0, >= 0.0.9)
    web-console (4.0.4)
      actionview (>= 6.0.0)
      activemodel (>= 6.0.0)
      bindex (>= 0.4.0)
      railties (>= 6.0.0)
    webdrivers (4.4.1)
      nokogiri (~> 1.6)
      rubyzip (>= 1.3.0)
      selenium-webdriver (>= 3.0, < 4.0)
    webpacker (4.3.0)
      activesupport (>= 4.2)
      rack-proxy (>= 0.6.1)
      railties (>= 4.2)
    websocket-driver (0.7.3)
      websocket-extensions (>= 0.1.0)
    websocket-extensions (0.1.5)
    xpath (3.2.0)
      nokogiri (~> 1.8)
    zeitwerk (2.4.0)

PLATFORMS
  ruby

DEPENDENCIES
  activejob-uniqueness
  bootsnap (>= 1.4.2)
  byebug
  capybara (>= 2.15)
  jbuilder (~> 2.7)
  listen (~> 3.2)
  mysql2
  octokit (~> 4.0)
  pry-byebug
  puma (~> 4.1)
  rails (~> 6.0.3, >= 6.0.3.2)
  redis (~> 4.0)
  sass-rails (>= 5)
  selenium-webdriver
  shipit-engine!
  sidekiq
  spring
  spring-watcher-listen (~> 2.0.0)
  turbolinks (~> 5)
  tzinfo-data
  web-console (>= 3.3.0)
  webdrivers
  webpacker (~> 4.0)

RUBY VERSION
   ruby 2.6.4p104

BUNDLED WITH
   1.17.3

@sharshenov
Copy link
Member

@eldad87 @eldadvcita Thank you for the report. Unfortunately, I can not reproduce this bug.

Here is my setup:

~ ruby -v
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
~ rails -v
Rails 6.0.3.4

Redis is flushed:

~ redis-cli FLUSHDB
OK

Dummy rails application with sidekiq + activejob-uniqueness and :until_and_while_executing strategy:

~ rails new test-activejob-uniqueness -G -O -M -P -T --api --skip-spring --skip-listen --skip-bootsnap --quiet \
 && cd test-activejob-uniqueness \
 && bundle add sidekiq activejob-uniqueness \
 && rails g job my_job \
 && echo "require 'sidekiq/web'\nRails.application.routes.draw { mount Sidekiq::Web => '/' }" > config/routes.rb \
 && sed -i '2 a\ \ unique :until_and_while_executing' app/jobs/my_job.rb \
 && sed -i '1 a\ \ config.active_job.queue_adapter = :sidekiq' config/environments/development.rb \
 && bundle exec rails s

At this point http://localhost:3000/queues shows no queues

Running console (cd ~/test-activejob-uniqueness && bundle exec rails c) in separate terminal session and creating jobs:

 :001 > MyJob.perform_later(123)
 => #<MyJob:0x000055a288263740 @arguments=[123], @job_id="4a37e212-329f-4d9c-baff-6d036ece87e2", @queue_name="default", @priority=nil, @executions=0, @exception_executions={}, @lock_strategy=#<ActiveJob::Uniqueness::Strategies::UntilAndWhileExecuting:0x000055a28823b3f8 @lock_key="activejob_uniqueness:my_job:4951b9977632a52fcd6f0cc65c57bb33", @lock_ttl=86400000, @on_conflict=:raise, @job=#<MyJob:0x000055a288263740 ...>, @runtime_lock_ttl=86400000, @on_runtime_conflict=:raise>, @provider_job_id="2d9274e00d2d2df1e582876a">

# The first job is enqueued successfully. I can see it in Sidekiq Web UI in the "default" queue

 :002 > MyJob.perform_later(123)
Traceback (most recent call last):
        1: from (irb):2
ActiveJob::Uniqueness::JobNotUnique (Not unique MyJob (Job ID: 1036d60c-a95f-489b-b816-bcd5c915d49c) (Lock key: activejob_uniqueness:my_job:4951b9977632a52fcd6f0cc65c57bb33) [123])

# The second attempt to enqueue the same job fails because it is not unique (as expected)
# Now I'm deleting exact job in the "default" queue via Sidekiq Web UI and making another attempt to enqueue the job:

 :003 > MyJob.perform_later(123)
 => #<MyJob:0x000055a287f25c18 @arguments=[123], @job_id="36e28acf-241e-49da-894f-9320e874c9e5", @queue_name="default", @priority=nil, @executions=0, @exception_executions={}, @lock_strategy=#<ActiveJob::Uniqueness::Strategies::UntilAndWhileExecuting:0x000055a287f24e30 @lock_key="activejob_uniqueness:my_job:4951b9977632a52fcd6f0cc65c57bb33", @lock_ttl=86400000, @on_conflict=:raise, @job=#<MyJob:0x000055a287f25c18 ...>, @runtime_lock_ttl=86400000, @on_runtime_conflict=:raise>, @provider_job_id="e8045a3251f1a9f70cceb2e8">

# The job is enqueued and I can see it in Sidekiq Web UI

 :004 > MyJob.perform_later(123)
Traceback (most recent call last):
        1: from (irb):4
ActiveJob::Uniqueness::JobNotUnique (Not unique MyJob (Job ID: 3fcc9f6f-bb3f-4fc3-8a6e-4f13968a7c37) (Lock key: activejob_uniqueness:my_job:4951b9977632a52fcd6f0cc65c57bb33) [123])

# Another attempt is prevented (as expected)
# This time I delete the "default" queue completely via Sidekiq Web UI and making another attempt to enqueue the job:

 :005 > MyJob.perform_later(123)
 => #<MyJob:0x000055a2877d2fb0 @arguments=[123], @job_id="4dfe2809-f98e-4f2d-91cf-4018df093c54", @queue_name="default", @priority=nil, @executions=0, @exception_executions={}, @lock_strategy=#<ActiveJob::Uniqueness::Strategies::UntilAndWhileExecuting:0x000055a2878036d8 @lock_key="activejob_uniqueness:my_job:4951b9977632a52fcd6f0cc65c57bb33", @lock_ttl=86400000, @on_conflict=:raise, @job=#<MyJob:0x000055a2877d2fb0 ...>, @runtime_lock_ttl=86400000, @on_runtime_conflict=:raise>, @provider_job_id="04921b021bb84f097e991c01">

# And it is enqueued successfully

Could you please try to reproduce the bug with this dummy application?

@jarkko
Copy link

jarkko commented Dec 16, 2020

Just FYI, we have a similar issue where the lock remains intact even though there are no jobs whatsoever either queued up or processing. Haven't been able to pinpoint the source of the stale lock yet, though, but it happens randomly yet almost daily.

My hunch is that restarting the Heroku worker dynos does not properly cause the locks to be released. I have no hard evidence to prove this, but it often seems to coincide chronologically with our nightly reboots.

@Aryk
Copy link

Aryk commented Oct 19, 2021

@jarkko I might be experiencing a similar issue. How do you know if a lock is still "in tact". Is there a method call?

@jarkko
Copy link

jarkko commented Oct 19, 2021

@Aryk I don't remember exactly anymore, but I believe you know at least because you're getting the ActiveJob::Uniqueness::JobNotUnique exception when trying to enqueue a job with the same paeans.

@damuz91
Copy link

damuz91 commented Nov 19, 2021

Dear fellows.
Just installed this gem (Love it so far), and i am having exact this behaviour.
What i did is that through the Sidekiq UI i deleted all enqueued jobs, then restarted the Sidekiq process, then enqueueing new jobs were throwing the NotUnique exception:

Not unique Sync::PartialSyncJob (Job ID: ec427d0a-f147-444a-b90b-34aeb4fc93a6) (Lock key: activejob_uniqueness:sync/partial_sync_job:709f23b19a9c8c8f0084a6fdde0a4b95) with arguments: 1275

So i had to manually go over all Job classes and use the .unlock method.

Any ideas on how to release the lock when deleting jobs from the queue?

@vinhboy
Copy link

vinhboy commented Jan 6, 2022

Not a real solution, but using ActiveJob::Uniqueness.unlock! is a quick fix in this situation.

@sharshenov
Copy link
Member

In case when the worker process is killed (e.g. kill -9 PID), it has no chance to remove the lock. Redis is a simple storage and it does not support deleting keys of dead clients. https://groups.google.com/g/redis-db/c/GkaYO73vb6w

My suggestion is to set a custom TTL per type of a job. Let's assume you know that the average job processing time is 5 seconds. Set the a custom TTL of 10 minutes then. In case of killed worker, there will be up to 10 minutes delay before the job will be unlocked automatically. But be careful with setting small values for the TTL. The queue latency should also be taken into account. If the queue is not utilized in time and its latency is over the TTL, jobs duplicates might appear.
Therefore, I recommend set the TTL per type of a job - it is an ad-hoc trade-off between commitment of queue utilization and tolerance to dead workers.

class MyJob < ActiveJob::Base
  unique :until_executing, lock_ttl: 10.minutes

  def perform(args)
    # work
  end
end

@navnathsd
Copy link

Not a real solution, but using ActiveJob::Uniqueness.unlock! is a quick fix in this situation.

Wow this was the great command it has worked for me.

@andrey-lizunov
Copy link

Same issue, manually deleting a job from the list of Scheduled Jobs in Sidekiq UI does not remove the lock. :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants