sidekiq-4.0.1/0000755000175600017570000000000012631157272012214 5ustar pravipravisidekiq-4.0.1/Pro-3.0-Upgrade.md0000644000175600017570000000260412631157272015163 0ustar pravipravi# Welcome to Sidekiq Pro 3.0! Sidekiq Pro 3.0 is designed to work with Sidekiq 4.0. ## What's New * **Redis 2.8.0 or greater is required.** Redis 2.8 was released two years ago and contains **many** useful features which Sidekiq couldn't leverage until now. **Redis 3.0.3 or greater is recommended** for large scale use. * Sidekiq Pro no longer uses Celluloid. If your application code uses Celluloid, you will need to pull it in yourself. * Pausing and unpausing queues is now instantaneous, no more polling! * Reliable fetch has been re-implemented due to the fetch changes in Sidekiq 4.0. * Support for platforms without persistent hostnames. Since reliable fetch normally requires a persistent hostname, you may disable hostname usage on platforms like Heroku and Docker: ```ruby Sidekiq.configure_server do |config| config.options[:ephemeral_hostname] = true config.reliable_fetch! end ``` This option is enabled automatically if Heroku's DYNO environment variable is present. Without a persistent hostname, each Sidekiq process **must** have its own unique index. * The old 'sidekiq/notifications' features have been removed. ## Upgrade First, make sure you are using Redis 2.8 or greater. Next: * Upgrade to the latest Sidekiq Pro 2.x. ```ruby gem 'sidekiq-pro', '< 3' ``` * Fix any deprecation warnings you see. * Upgrade to 3.x. ```ruby gem 'sidekiq-pro', '< 4' ``` sidekiq-4.0.1/LICENSE0000644000175600017570000000063012631157272013220 0ustar pravipraviCopyright (c) Contributed Systems LLC Sidekiq is an Open Source project licensed under the terms of the LGPLv3 license. Please see for license text. Sidekiq Pro has a commercial-friendly license allowing private forks and modifications of Sidekiq. Please see http://sidekiq.org/pro/ for more detail. You can find the commercial license terms in COMM-LICENSE. sidekiq-4.0.1/Gemfile0000644000175600017570000000104712631157272013511 0ustar pravipravisource 'https://rubygems.org' gemspec gem 'rails', '~> 4.2' gem 'simplecov' gem 'minitest' gem 'minitest-utils' gem 'toxiproxy' platforms :rbx do gem 'rubysl', '~> 2.0' # if using anything in the ruby standard library gem 'psych' # if using yaml gem 'rubinius-developer_tools' # if using any of coverage, debugger, profiler end platforms :ruby do gem 'sqlite3' end platforms :mri do gem 'pry-byebug' gem 'ruby-prof' end platforms :jruby do gem 'jruby-openssl' gem 'activerecord-jdbcsqlite3-adapter' end sidekiq-4.0.1/web/0000755000175600017570000000000012631157272012771 5ustar pravipravisidekiq-4.0.1/web/views/0000755000175600017570000000000012631157272014126 5ustar pravipravisidekiq-4.0.1/web/views/_poll_js.erb0000644000175600017570000000020012631157272016411 0ustar pravipravi<% if current_path != '' && params[:poll] %> <% end %> sidekiq-4.0.1/web/views/retries.erb0000644000175600017570000000546512631157272016307 0ustar pravipravi

<%= t('Retries') %>

<% if @retries.size > 0 && @total_size > @count %>
<%= erb :_paging, :locals => { :url => "#{root_path}retries" } %>
<% end %> <%= filtering('retries') %>
<% if @retries.size > 0 %>
<%= csrf_tag %>
<% @retries.each do |entry| %> <% end %>
<%= t('NextRetry') %> <%= t('RetryCount') %> <%= t('Queue') %> <%= t('Job') %> <%= t('Arguments') %> <%= t('Error') %>
<%= relative_time(entry.at) %> <%= entry['retry_count'] %> <%= entry.queue %> <%= entry.display_class %>
<%= display_args(entry.display_args) %>
<%= h truncate("#{entry['error_class']}: #{entry['error_message']}", 200) %>
<%= csrf_tag %>
<%= csrf_tag %>
<% else %>
<%= t('NoRetriesFound') %>
<% end %> sidekiq-4.0.1/web/views/_summary.erb0000644000175600017570000000267212631157272016463 0ustar pravipravi sidekiq-4.0.1/web/views/_footer.erb0000644000175600017570000000111012631157272016246 0ustar pravipravi sidekiq-4.0.1/web/views/_status.erb0000644000175600017570000000016612631157272016305 0ustar pravipravi <%= t(current_status) %> sidekiq-4.0.1/web/views/morgue.erb0000644000175600017570000000514012631157272016116 0ustar pravipravi

<%= t('DeadJobs') %>

<% if @dead.size > 0 && @total_size > @count %>
<%= erb :_paging, :locals => { :url => "#{root_path}morgue" } %>
<% end %> <%= filtering('dead') %>
<% if @dead.size > 0 %>
<%= csrf_tag %>
<% @dead.each do |entry| %> <% end %>
<%= t('LastRetry') %> <%= t('Queue') %> <%= t('Job') %> <%= t('Arguments') %> <%= t('Error') %>
<%= relative_time(entry.at) %> <%= entry.queue %> <%= entry.display_class %>
<%= display_args(entry.display_args) %>
<%= h truncate("#{entry['error_class']}: #{entry['error_message']}", 200) %>
<%= csrf_tag %>
<%= csrf_tag %>
<% else %>
<%= t('NoDeadJobsFound') %>
<% end %> sidekiq-4.0.1/web/views/busy.erb0000644000175600017570000000565012631157272015610 0ustar pravipravi

<%= t('Processes') %>

<%= csrf_tag %>
<% processes.each do |process| %> <% end %>
<%= t('Name') %> <%= t('Started') %> <%= t('Threads') %> <%= t('Busy') %>  
<%= "#{process['hostname']}:#{process['pid']}" %> <%= process.tag %> <% process.labels.each do |label| %> <%= label %> <% end %>
<%= "#{t('Queues')}: " %> <%= process['queues'] * ", " %>
<%= relative_time(Time.at(process['started_at'])) %> <%= process['concurrency'] %> <%= process['busy'] %>
<%= csrf_tag %>

<%= t('Jobs') %>

<% workers.each do |process, thread, msg| %> <% job = Sidekiq::Job.new(msg['payload']) %> <% end %>
<%= t('Process') %> <%= t('TID') %> <%= t('JID') %> <%= t('Queue') %> <%= t('Job') %> <%= t('Arguments') %> <%= t('Started') %>
<%= process %> <%= thread %> <%= job.jid %> <%= msg['queue'] %> <%= job.display_class %>
<%= display_args(job.display_args) %>
<%= relative_time(Time.at(msg['run_at'])) %>
sidekiq-4.0.1/web/views/_paging.erb0000644000175600017570000000152512631157272016227 0ustar pravipravi<% if @total_size > @count %> <% end %> sidekiq-4.0.1/web/views/queue.erb0000644000175600017570000000326112631157272015746 0ustar pravipravi

<%= t('CurrentMessagesInQueue', :queue => h(@name)) %> <% if @queue.paused? %> <%= t('Paused') %> <% end %>

<%= erb :_paging, :locals => { :url => "#{root_path}queues/#{@name}" } %>
<% @messages.each_with_index do |msg, index| %> <% end %>
<%= t('Job') %> <%= t('Arguments') %>
<%= h(msg.display_class) %> <% a = msg.display_args.inspect %> <% if a.size > 100 %> <%= h(msg.display_args.inspect[0..100]) + "... " %> <% else %> <%= h(msg.display_args) %> <% end %>
<%= csrf_tag %>
<%= erb :_paging, :locals => { :url => "#{root_path}queues/#{@name}" } %> sidekiq-4.0.1/web/views/retry.erb0000644000175600017570000000215712631157272015772 0ustar pravipravi<%= erb :_job_info, :locals => {:job => @retry, :type => :retry} %>

<%= t('Error') %>

<% if !@retry['error_backtrace'].nil? %> <% end %>
<%= t('ErrorClass') %> <%= @retry['error_class'] %>
<%= t('ErrorMessage') %> <%= h(@retry['error_message']) %>
<%= t('ErrorBacktrace') %> <%= @retry['error_backtrace'].join("
") %>
<%= csrf_tag %> <%= t('GoBack') %>
sidekiq-4.0.1/web/views/_nav.erb0000644000175600017570000000476412631157272015556 0ustar pravipravi sidekiq-4.0.1/web/views/scheduled_job_info.erb0000644000175600017570000000075112631157272020430 0ustar pravipravi<%= erb :_job_info, :locals => {:job => @job, :type => :scheduled} %>
<%= csrf_tag %> <%= t('GoBack') %>
sidekiq-4.0.1/web/views/layout.erb0000644000175600017570000000225412631157272016140 0ustar pravipravi <%= environment_title_prefix %><%= Sidekiq::NAME %> <%= display_custom_head %> <%= erb :_nav %>
<%= erb :_summary %>
<%= yield %>
<%= erb :_footer %> <%= erb :_poll_js %> sidekiq-4.0.1/web/views/_poll_link.erb0000644000175600017570000000053112631157272016741 0ustar pravipravi<% if current_path != '' %> <% if params[:poll] %> <%= t('StopPolling') %> <% else %> <%= t('LivePoll') %> <% end %> <% end %> sidekiq-4.0.1/web/views/scheduled.erb0000644000175600017570000000352412631157272016564 0ustar pravipravi

<%= t('ScheduledJobs') %>

<% if @scheduled.size > 0 && @total_size > @count %>
<%= erb :_paging, :locals => { :url => "#{root_path}scheduled" } %>
<% end %> <%= filtering('scheduled') %>
<% if @scheduled.size > 0 %>
<%= csrf_tag %>
<% @scheduled.each do |entry| %> <% end %>
<%= t('When') %> <%= t('Queue') %> <%= t('Job') %> <%= t('Arguments') %>
<%= relative_time(entry.at) %> <%= entry.queue %> <%= entry.display_class %>
<%= display_args(entry.display_args) %>
<% else %>
<%= t('NoScheduledFound') %>
<% end %> sidekiq-4.0.1/web/views/dashboard.erb0000644000175600017570000000555112631157272016555 0ustar pravipravi

<%= t('Dashboard') %>

<%= t('PollingInterval') %>: 2 sec
<%= t('History') %> "><%= t('OneWeek') %> "><%= t('OneMonth') %> "><%= t('ThreeMonths') %> "><%= t('SixMonths') %>

Redis
<% if @redis_info.fetch("redis_version", nil) %>

<%= @redis_info.fetch("redis_version") %>

<%= t('Version') %>

<% end %> <% if @redis_info.fetch("uptime_in_days", nil) %>

<%= @redis_info.fetch("uptime_in_days") %>

<%= t('Uptime') %>

<% end %> <% if @redis_info.fetch("connected_clients", nil) %>

<%= @redis_info.fetch("connected_clients") %>

<%= t('Connections') %>

<% end %> <% if @redis_info.fetch("used_memory_human", nil) %>

<%= @redis_info.fetch("used_memory_human") %>

<%= t('MemoryUsage') %>

<% end %> <% if @redis_info.fetch("used_memory_peak_human", nil) %>

<%= @redis_info.fetch("used_memory_peak_human") %>

<%= t('PeakMemoryUsage') %>

<% end %>
sidekiq-4.0.1/web/views/queues.erb0000644000175600017570000000174412631157272016135 0ustar pravipravi

<%= t('Queues') %>

<% @queues.each do |queue| %> <% end %>
<%= t('Queue') %> <%= t('Size') %> <%= t('Actions') %>
<%= queue.name %> <% if queue.paused? %> <%= t('Paused') %> <% end %> <%= number_with_delimiter(queue.size) %>
<%= csrf_tag %>
sidekiq-4.0.1/web/views/_job_info.erb0000644000175600017570000000444212631157272016550 0ustar pravipravi

<%= t('Job') %>

<% unless retry_extra_items(job).empty? %> <% end %> <% if type == :retry %> <% if job['retry_count'] && job['retry_count'] > 0 %> <% else %> <% end %> <% end %> <% if type == :scheduled %> <% end %> <% if type == :dead %> <% end %>
<%= t('Queue') %> <%= job.queue %>
<%= t('Job') %> <%= job.display_class %>
<%= t('Arguments') %>
<%= display_args(job.display_args, nil) %>
JID <%= job.jid %>
<%= t('Enqueued') %> <%= (enqueued_at = job.enqueued_at) ? relative_time(enqueued_at) : t('NotYetEnqueued') %>
<%= t('Extras') %> <%= retry_extra_items(job).inspect %>
<%= t('RetryCount') %> <%= job['retry_count'] %>
<%= t('LastRetry') %> <%= relative_time(Time.at(job['retried_at'])) %>
<%= t('OriginallyFailed') %> <%= relative_time(Time.at(job['failed_at'])) %>
<%= t('NextRetry') %> <%= relative_time(job.at) %>
<%= t('Scheduled') %> <%= relative_time(job.at) %>
<%= t('LastRetry') %> <%= relative_time(job.at) %>
sidekiq-4.0.1/web/views/dead.erb0000644000175600017570000000214512631157272015517 0ustar pravipravi<%= erb :_job_info, :locals => {:job => @dead, :type => :dead} %>

<%= t('Error') %>

<% if !@dead['error_backtrace'].nil? %> <% end %>
<%= t('ErrorClass') %> <%= @dead['error_class'] %>
<%= t('ErrorMessage') %> <%= h(@dead['error_message']) %>
<%= t('ErrorBacktrace') %> <%= @dead['error_backtrace'].join("
") %>
<%= csrf_tag %> <%= t('GoBack') %>
sidekiq-4.0.1/web/locales/0000755000175600017570000000000012631157272014413 5ustar pravipravisidekiq-4.0.1/web/locales/el.yml0000644000175600017570000000527312631157272015545 0ustar pravipravi# elements like %{queue} are variables and should not be translated el: # <---- change this to your locale code Dashboard: Πίνακας Ελέγχου Status: Κατάσταση Time: Χρόνος Namespace: Namespace Realtime: Τρέχουσα Κατάσταση History: Ιστορικό Busy: Απασχολημένο Processed: Επεξεργάστηκε Failed: Απέτυχε Scheduled: Προγραματίστηκε Retries: Προσπάθειες Enqueued: Μπήκαν στην στοίβα Worker: Εργάτης LivePoll: Τρέχουσα Κατάσταση StopPolling: Διακοπή Τρέχουσας Κατάστασης Queue: Στοίβα Class: Κλάση Job: Εργασία Arguments: Ορίσματα Extras: Extras Started: Ξεκίνησαν ShowAll: Εμφάνιση Όλων CurrentMessagesInQueue: Τρέχουσες εργασίες %{queue} Delete: Διαγραφή AddToQueue: Προσθήκη στην στοίβα AreYouSureDeleteJob: Θέλετε να διαγράψετε την εργασία αυτη; AreYouSureDeleteQueue: Θέλετε να διαγράψετε την %{queue} στοίβα? Queues: Στοίβες Size: Μέγεθος Actions: Ενέργειες NextRetry: Επόμενη προσπάθεια RetryCount: Αριθμός προσπαθειών RetryNow: Προσπάθησε τώρα LastRetry: Τελευταία προσπάθεια OriginallyFailed: Αρχικές Αποτυχίες AreYouSure: Είστε σίγουρος? DeleteAll: Διαγραφή όλων RetryAll: Επανάληψη Όλων NoRetriesFound: Δεν βρέθηκαν προσπάθειες Error: Σφάλμα ErrorClass: Κλάση σφάλματος ErrorMessage: Μήνυμα Σφάλματος ErrorBacktrace: Σφάλμα Backtrace GoBack: ← Πίσω NoScheduledFound: Δεν βρέθηκαν προγραμματισμένες εργασίες When: Πότε ScheduledJobs: Προγραμματισμένες Εργασίες idle: αδρανής active: ενεργή Version: Έκδοση Connections: Συνδέσεις MemoryUsage: Χρήση Μνήμης PeakMemoryUsage: Μέγιστη Χρήση Μνήμης Uptime: Διάρκεια Λειτουργείας (ημέρες) OneWeek: 1 εβδομάδα OneMonth: 1 μήνας ThreeMonths: 3 μήνες SixMonths: 6 μήνες Failures: Αποτυχίες DeadJobs: Αδρανείς Εργασίες NoDeadJobsFound: Δεν βρέθηκαν αδρανείς εργασίες Dead: Αδρανείς Processes: Διεργασίες Thread: Νήμα Threads: Νήματα Jobs: Εργασίες sidekiq-4.0.1/web/locales/zh-tw.yml0000644000175600017570000000344412631157272016214 0ustar pravipravi# elements like %{queue} are variables and should not be translated zh-tw: # <---- change this to your locale code Dashboard: 資訊主頁 Status: 狀態 Time: 時間 Namespace: 命名空間 Realtime: 即時 History: 歷史資料 Busy: 忙碌 Processed: 已處理 Failed: 已失敗 Scheduled: 已排程 Retries: 重試 Enqueued: 已佇列 Worker: 工人 LivePoll: 即時輪詢 StopPolling: 停止輪詢 Queue: 佇列 Class: 類別 Job: 工作 Arguments: 參數 Extras: 額外的 Started: 已開始 ShowAll: 顯示全部 CurrentMessagesInQueue: 目前在%{queue}的工作 Delete: 刪除 AddToQueue: 增加至佇列 AreYouSureDeleteJob: 你確定要刪除這個工作嗎? AreYouSureDeleteQueue: 你確定要刪除%{queue}這個佇列? Queues: 佇列 Size: 容量 Actions: 動作 NextRetry: 下次重試 RetryCount: 重試次數 RetryNow: 馬上重試 LastRetry: 最後一次重試 OriginallyFailed: 原本已失敗 AreYouSure: 你確定? DeleteAll: 刪除全部 RetryAll: 重試全部 NoRetriesFound: 沒有發現可重試 Error: 錯誤 ErrorClass: 錯誤類別 ErrorMessage: 錯誤訊息 ErrorBacktrace: 錯誤的回調追踨 GoBack: ← 返回 NoScheduledFound: 沒有發現已排程的工作 When: 當 ScheduledJobs: 已排程的工作 idle: 閒置 active: 活動中 Version: 版本 Connections: 連線 MemoryUsage: 記憶體使用量 PeakMemoryUsage: 尖峰記憶體使用量 Uptime: 上線時間 (天數) OneWeek: 一週 OneMonth: 一個月 ThreeMonths: 三個月 SixMonths: 六個月 Failures: 失敗 DeadJobs: 停滯工作 NoDeadJobsFound: 沒有發現任何停滯的工作 Dead: 停滯 Processes: 處理中 Thread: 執行緒 Threads: 執行緒 Jobs: 工作 sidekiq-4.0.1/web/locales/ko.yml0000644000175600017570000000343412631157272015553 0ustar pravipravi# elements like %{queue} are variables and should not be translated ko: Dashboard: 대시보드 Status: 상태 Time: 시간 Namespace: 네임스페이스 Realtime: 실시간 History: 히스토리 Busy: 작동 Processed: 처리완료 Failed: 실패 Scheduled: 예약 Retries: 재시도 Enqueued: 대기 중 Worker: 워커 LivePoll: 폴링 시작 StopPolling: 폴링 중단 Queue: 큐 Class: 클래스 Job: 작업 Arguments: 인자 Started: 시작 ShowAll: 모두 보기 CurrentMessagesInQueue: %{queue}에 대기 중인 메시지 Delete: 삭제 AddToQueue: 큐 추가 AreYouSureDeleteJob: 이 작업을 삭제하시겠습니까? AreYouSureDeleteQueue: 이 %{queue} 큐를 삭제하시겠습니까? Queues: 큐 Size: 크기 Actions: 동작 NextRetry: 다음 재시도 RetryCount: 재시도 횟수 RetryNow: 지금 재시도 LastRetry: 최근 재시도 OriginallyFailed: 실패 AreYouSure: 정말입니까? DeleteAll: 모두 삭제 RetryAll: 모두 재시도 NoRetriesFound: 재시도 내역이 없습니다 Error: 에러 ErrorClass: 에러 클래스 ErrorMessage: 에러 메시지 ErrorBacktrace: 에러 Backtrace GoBack: ← 뒤로 NoScheduledFound: 예약된 작업이 없습니다 When: 언제 ScheduledJobs: 예약된 작업 idle: 대기 중 active: 동작 중 Version: 버전 Connections: 커넥션 MemoryUsage: 메모리 사용량 PeakMemoryUsage: 최대 메모리 사용량 Uptime: 업타임 (일) OneWeek: 1 주 OneMonth: 1 달 ThreeMonths: 3 달 SixMonths: 6 달 Batches: 배치 Failures: 실패 DeadJobs: 죽은 작업 NoDeadJobsFound: 죽은 작업이 없습니다 Dead: 죽음 Processes: 프로세스 Thread: 스레드 Threads: 스레드 Jobs: 작업 sidekiq-4.0.1/web/locales/uk.yml0000644000175600017570000000543612631157272015565 0ustar pravipraviuk: Dashboard: Панель керування Status: Статус Time: Час Namespace: Простір імен Realtime: Зараз History: Історія Busy: Зайнятих Processed: Опрацьовано Failed: Невдалих Scheduled: Заплановано Retries: Спроби Enqueued: У черзі Worker: Обробник LivePoll: Постійне опитування StopPolling: Зупинити опитування Queue: Черга Class: Клас Job: Задача Arguments: Аргументи Extras: Додатково Started: Запущено ShowAll: Відобразити усі CurrentMessagesInQueue: Поточні задачі у черзі %{queue} Delete: Видалити AddToQueue: Додати до черги AreYouSureDeleteJob: Ви впевнені у тому, що хочете видалити задачу? AreYouSureDeleteQueue: Ви впевнені у тому, що хочете видалити чергу %{queue}? Queues: Черги Size: Розмір Actions: Дії NextRetry: Наступна спроба RetryCount: Кількість спроб RetryNow: Повторити зараз Kill: Вбиваємо LastRetry: Остання спроба OriginallyFailed: Перша невдала спроба AreYouSure: Ви впевнені? DeleteAll: Видалити усі RetryAll: Повторити усі NoRetriesFound: Спроб не знайдено Error: Помилка ErrorClass: Клас помилки ErrorMessage: Повідомлення про помилку ErrorBacktrace: Трасування помилки GoBack: ← Назад NoScheduledFound: Запланованих задач не знайдено When: Коли ScheduledJobs: Заплановані задачі idle: незайнятий active: активний Version: Версія Connections: З'єднань MemoryUsage: Використання пам'яті PeakMemoryUsage: Максимальне використання пам'яті Uptime: Днів безперебійної роботи OneWeek: 1 тиждень OneMonth: 1 місяць ThreeMonths: 3 місяці SixMonths: 6 місяців Failures: Невдачі DeadJobs: Вбиті задачі NoDeadJobsFound: Вбитих задач не знайдено Dead: Вбитих Processes: Процеси Thread: Потік Threads: Потоки Jobs: Задачі Paused: Призупинено Stop: Зупинити Quiet: Призупинити StopAll: Зупинити усі QuietAll: Призупинити усі PollingInterval: Інтервал опитування Plugins: Плагіни NotYetEnqueued: Ще не в черзі sidekiq-4.0.1/web/locales/pl.yml0000644000175600017570000000316112631157272015552 0ustar pravipravi# elements like %{queue} are variables and should not be translated pl: Dashboard: Kokpit Status: Status Time: Czas Namespace: Przestrzeń nazw Realtime: Czas rzeczywisty History: Historia Busy: Zajęte Processed: Ukończone Failed: Nieudane Scheduled: Zaplanowane Retries: Prób Enqueued: Zakolejkowane Worker: Worker LivePoll: Wczytuj na żywo StopPolling: Zatrzymaj wczytywanie na żywo Queue: Kolejka Class: Klasa Job: Zadanie Arguments: Argumenty Started: Rozpoczęte ShowAll: Pokaż wszystko CurrentMessagesInQueue: Aktualne wiadomości w kolejce %{queue} Delete: Usuń AddToQueue: dodaj do kolejki AreYouSureDeleteJob: Czy na pewno usunąć to zadanie? AreYouSureDeleteQueue: Czy na pewno usunąć kolejkę %{queue}? Queues: Kolejki Size: Rozmiar Actions: Akcje NextRetry: Kolejna próba RetryCount: Liczba prób RetryNow: Ponów teraz LastRetry: Ostatnie ponowienie OriginallyFailed: Ostatnio nieudane AreYouSure: Na pewno? DeleteAll: Usuń wszystko RetryAll: Powtórz wszystko NoRetriesFound: Brak powtórzeń Error: Błąd ErrorClass: Klasa błędu ErrorMessage: Wiadomosć błędu ErrorBacktrace: Wyjście błędu GoBack: ← Wróć NoScheduledFound: Brak zaplanowanych zadań When: Kiedy ScheduledJobs: Zaplanowane zadania idle: bezczynne active: aktywne Version: Wersja Connections: Połączenia MemoryUsage: Wykorzystanie pamięci PeakMemoryUsage: Największe wykorzystanie pamięci Uptime: Uptime (dni) OneWeek: 1 tydzień OneMonth: 1 miesiąc ThreeMonths: 3 miesiące SixMonths: 6 miesięcy sidekiq-4.0.1/web/locales/hi.yml0000644000175600017570000000627612631157272015551 0ustar pravipravi# elements like %{queue} are variables and should not be translated hi: Dashboard: डैशबोर्ड Status: स्थिती Time: समय Namespace: नामस्थान Realtime: रिअल टाईम History: वृत्तान्त Busy: व्यस्थ Processed: कार्रवाई कृत Failed: असफल Scheduled: परिगणित Retries: पुनर्प्रयास Enqueued: कतारबद्ध Worker: वर्कर LivePoll: लाईव सर्वेक्षण StopPolling: सर्वेक्षण रोको Queue: कतार Class: क्लास Job: कार्य Arguments: अर्गुमेन्ट्स् Extras: अतिरिक्त Started: शुरु हुआ ShowAll: सब दिखाएं CurrentMessagesInQueue: %{queue} कतार मे वर्तमान कार्य Delete: हटाओ AddToQueue: कतार मे जोड़ें AreYouSureDeleteJob: क्या आप इस कार्य को हटाना चाहते है? AreYouSureDeleteQueue: क्या आप %{queue} कतार को हटाना चाहते है? Queues: कतारे Size: आकार Actions: कार्रवाई NextRetry: अगला पुन:प्रयास RetryCount: पुन:प्रयास संख्या RetryNow: पुन:प्रयास करे Kill: नष्ट करे LastRetry: अंतिम पुन:प्रयास OriginallyFailed: पहिले से विफल AreYouSure: क्या आपको यकीन है? DeleteAll: सब हटाओ RetryAll: सब पुन:प्रयास करे NoRetriesFound: कोई पुनर्प्रयास नही पाए गए Error: एरर ErrorClass: एरर क्लास ErrorMessage: एरर संदेश ErrorBacktrace: एरर बैकट्रेस GoBack: ← पीछे NoScheduledFound: कोई परिगणित कार्य नही पाए गए When: कब ScheduledJobs: परिगणित कार्य idle: निष्क्रिय active: सक्रिय Version: वर्जन Connections: कनेक्श्न MemoryUsage: मेमरी उपयोग PeakMemoryUsage: अधिकतम मेमरी उपयोग Uptime: उपरिकाल (दिवस) OneWeek: १ सप्ताह OneMonth: १ महीना ThreeMonths: ३ महीने SixMonths: ६ महीने Failures: असफलता DeadJobs: निष्प्राण कार्य NoDeadJobsFound: कोई निष्प्राण कार्य नही पाए गए Dead: निष्प्राण Processes: प्रोसेसेस् Thread: थ्रेड Threads: थ्रेड्स् Jobs: कार्य Paused: थमे हुए Stop: रोको Quiet: शांत करो StopAll: सब रोको QuietAll: सब शांत करो PollingInterval: सर्वेक्षण अंतराल sidekiq-4.0.1/web/locales/ru.yml0000644000175600017570000000532312631157272015567 0ustar pravipraviru: Dashboard: Панель управления Status: Статус Time: Время Namespace: Пространство имен Realtime: Сейчас History: История Busy: Занят Processed: Обработано Failed: Провалено Scheduled: Запланировано Retries: Попытки Enqueued: В очереди Worker: Обработчик LivePoll: Постоянный опрос StopPolling: Остановить опрос Queue: Очередь Class: Класс Job: Задача Arguments: Аргументы Extras: Дополнительно Started: Запущено ShowAll: Показать все CurrentMessagesInQueue: Текущие задачи в очереди %{queue} Delete: Удалить AddToQueue: Добавить в очередь AreYouSureDeleteJob: Вы уверены, что хотите удалить эту задачу? AreYouSureDeleteQueue: Вы уверены, что хотите удалить очередь %{queue}? Queues: Очереди Size: Размер Actions: Действия NextRetry: Следующая попытка RetryCount: Кол-во попыток RetryNow: Повторить сейчас Kill: Убиваем LastRetry: Последняя попытка OriginallyFailed: Первый провал AreYouSure: Вы уверены? DeleteAll: Удалить все RetryAll: Повторить все NoRetriesFound: Нет попыток Error: Ошибка ErrorClass: Класс ошибки ErrorMessage: Сообщение об ошибке ErrorBacktrace: Трассировка ошибки GoBack: ← Назад NoScheduledFound: Нет запланированных задач When: Когда ScheduledJobs: Запланированные задачи idle: отдыхает active: активен Version: Версия Connections: Соединения MemoryUsage: Использование памяти PeakMemoryUsage: Максимальный расход памяти Uptime: Дня(ей) бесперебойной работы OneWeek: 1 неделя OneMonth: 1 месяц ThreeMonths: 3 месяца SixMonths: 6 месяцев Failures: Провалы DeadJobs: Убитые задачи NoDeadJobsFound: Нет убитых задач Dead: Убито Processes: Процессы Thread: Поток Threads: Потоки Jobs: Задачи Paused: Приостановлено Stop: Остановить Quiet: Отдыхать StopAll: Остановить все QuietAll: Отдыхать всем PollingInterval: Интервал опроса Plugins: Плагины sidekiq-4.0.1/web/locales/nl.yml0000644000175600017570000000353312631157272015553 0ustar pravipravi# elements like %{queue} are variables and should not be translated nl: Dashboard: Dashboard Status: Status Time: Tijd Namespace: Namespace Realtime: Real-time History: Geschiedenis Busy: Bezet Processed: Verwerkt Failed: Mislukt Scheduled: Gepland Retries: Opnieuw proberen Enqueued: In de wachtrij Worker: Werker LivePoll: Live bijwerken StopPolling: Stop live bijwerken Queue: Wachtrij Class: Klasse Job: Taak Arguments: Argumenten Extras: Extra's Started: Gestart ShowAll: Toon alle CurrentMessagesInQueue: Aantal berichten in %{queue} Delete: Verwijderen AddToQueue: Toevoegen aan wachtrij AreYouSureDeleteJob: Weet u zeker dat u deze taak wilt verwijderen? AreYouSureDeleteQueue: Weet u zeker dat u wachtrij %{queue} wilt verwijderen? Queues: Wachtrijen Size: Grootte Actions: Acties NextRetry: Volgende opnieuw proberen RetryCount: Aantal opnieuw geprobeerd RetryNow: Nu opnieuw proberen LastRetry: Laatste poging OriginallyFailed: Oorspronkelijk mislukt AreYouSure: Weet u het zeker? DeleteAll: Alle verwijderen RetryAll: Alle opnieuw proberen NoRetriesFound: Geen opnieuw te proberen taken gevonden Error: Fout ErrorClass: Fout Klasse ErrorMessage: Foutmelding ErrorBacktrace: Fout Backtrace GoBack: ← Terug NoScheduledFound: Geen geplande taken gevonden When: Wanneer ScheduledJobs: Geplande taken idle: inactief active: actief Version: Versie Connections: Verbindingen MemoryUsage: Geheugengebruik PeakMemoryUsage: Piek geheugengebruik Uptime: Looptijd (dagen) OneWeek: 1 week OneMonth: 1 maand ThreeMonths: 3 maanden SixMonths: 6 maanden Failures: Mislukt DeadJobs: Overleden taken NoDeadJobsFound: Geen overleden taken gevonden Dead: Overleden Processes: Processen Thread: Thread Threads: Threads Jobs: Taken sidekiq-4.0.1/web/locales/pt-br.yml0000644000175600017570000000344012631157272016163 0ustar pravipravi# elements like %{queue} are variables and should not be translated "pt-br": Dashboard: Painel Status: Status Time: Tempo Namespace: Namespace Realtime: Tempo real History: Histórico Busy: Ocupados Processed: Processados Failed: Falhas Scheduled: Agendados Retries: Tentativas Enqueued: Na fila Worker: Trabalhador LivePoll: Live Poll StopPolling: Parar Polling Queue: Fila Class: Classe Job: Tarefa Arguments: Argumentos Extras: Extras Started: Iniciados ShowAll: Mostrar todos CurrentMessagesInQueue: Mensagens atualmente na %{queue} Delete: Apagar AddToQueue: Adicionar à fila AreYouSureDeleteJob: Deseja deletar esta tarefa? AreYouSureDeleteQueue: Deseja deletar a %{queue} fila? Queues: Filas Size: Tamanho Actions: Ações NextRetry: Próxima Tentativa RetryCount: Número de Tentativas RetryNow: Tentar novamente agora LastRetry: Última tentativa OriginallyFailed: Falhou originalmente AreYouSure: Tem certeza? DeleteAll: Apagar tudo RetryAll: Tentar tudo novamente NoRetriesFound: Nenhuma tentativa encontrada Error: Erro ErrorClass: Classe de erro ErrorMessage: Mensagem de erro ErrorBacktrace: Rastreamento do erro GoBack: ← Voltar NoScheduledFound: Nenhuma tarefa agendada foi encontrada When: Quando ScheduledJobs: Tarefas agendadas idle: ocioso active: ativo Version: Versão Connections: Conexões MemoryUsage: Uso de memória PeakMemoryUsage: Pico de uso de memória Uptime: Dias rodando OneWeek: 1 semana OneMonth: 1 mês ThreeMonths: 3 meses SixMonths: 6 meses Failures : Falhas DeadJobs : Tarefas mortas NoDeadJobsFound : Nenhuma tarefa morta foi encontrada Dead : Morta Processes : Processos Thread : Thread Threads : Threads Jobs : Tarefas sidekiq-4.0.1/web/locales/fr.yml0000644000175600017570000000361512631157272015552 0ustar pravipravi# elements like %{queue} are variables and should not be translated fr: Dashboard: Tableau de Bord Status: État Time: Heure Namespace: Namespace Realtime: Temps réel History: Historique Busy: Occupées Processed: Traitées Failed: Échouées Scheduled: Planifiée Retries: Tentatives Enqueued: En queue Worker: Travailleur LivePoll: Temps réel StopPolling: Arrêt du temps réel Queue: Queue Class: Classe Job: Tâche Arguments: Arguments Extras: Extras Started: Démarrées ShowAll: Montrer tout CurrentMessagesInQueue: Messages actuellement dans %{queue} Delete: Supprimer AddToQueue: Ajouter à la queue AreYouSureDeleteJob: Êtes-vous certain de vouloir supprimer cette tâche ? AreYouSureDeleteQueue: Êtes-vous certain de vouloir supprimer la queue %{queue} ? Queues: Queues Size: Taille Actions: Actions NextRetry: Prochain essai RetryCount: Nombre d'essais RetryNow: Réessayer maintenant Kill: Tuer LastRetry: Dernier essai OriginallyFailed: Échec originel AreYouSure: Êtes-vous certain ? DeleteAll: Tout supprimer RetryAll: Tout réessayer NoRetriesFound: Aucune tâche à réessayer n’a été trouvée Error: Erreur ErrorClass: Classe d’erreur ErrorMessage: Message d’erreur ErrorBacktrace: Backtrace d’erreur GoBack: ← Retour NoScheduledFound: Pas de tâches planifiées trouvées When: Quand ScheduledJobs: Tâches planifiées idle: en attente active: actives Version: Version Connections: Connexions MemoryUsage: Mémoire utilisée PeakMemoryUsage: Mémoire utilisée (max.) Uptime: Uptime (jours) OneWeek: 1 semaine OneMonth: 1 mois ThreeMonths: 3 mois SixMonths: 6 mois Failures: Echecs DeadJobs: Tâches mortes NoDeadJobsFound: Aucune tâche morte n'a été trouvée Dead: Morte Processes: Processus Thread: Fil Threads: Fils Jobs: Tâches sidekiq-4.0.1/web/locales/it.yml0000644000175600017570000000350012631157272015550 0ustar pravipravi# elements like %{queue} are variables and should not be translated it: Dashboard: Dashboard Status: Stato Time: Ora Namespace: Namespace Realtime: Tempo reale History: Storia Busy: Occupato Processed: Processato Failed: Fallito Scheduled: Pianificato Retries: Nuovi tentativi Enqueued: In coda Worker: Lavoratore LivePoll: Live poll StopPolling: Ferma il polling Queue: Coda Class: Classe Job: Lavoro Arguments: Argomenti Extras: Extra Started: Iniziato ShowAll: Mostra tutti CurrentMessagesInQueue: Messaggi in %{queue} Delete: Cancella AddToQueue: Aggiungi alla coda AreYouSureDeleteJob: Sei sicuro di voler cancellare questo lavoro? AreYouSureDeleteQueue: Sei sicuro di voler cancellare la coda %{queue}? Queues: Code Size: Dimensione Actions: Azioni NextRetry: Prossimo tentativo RetryCount: Totale tentativi RetryNow: Riprova Kill: Uccidere LastRetry: Ultimo tentativo OriginallyFailed: Primo fallimento AreYouSure: Sei sicuro? DeleteAll: Cancella tutti RetryAll: Riprova tutti NoRetriesFound: Non sono stati trovati nuovi tentativi Error: Errore ErrorClass: Classe dell'errore ErrorMessage: Messaggio di errore ErrorBacktrace: Backtrace dell'errore GoBack: ← Indietro NoScheduledFound: Non ci sono lavori pianificati When: Quando ScheduledJobs: Lavori pianificati idle: inattivo active: attivo Version: Versione Connections: Connessioni MemoryUsage: Memoria utilizzata PeakMemoryUsage: Memoria utilizzata (max.) Uptime: Uptime (giorni) OneWeek: 1 settimana OneMonth: 1 mese ThreeMonths: 3 mesi SixMonths: 6 mesi Failures: Fallimenti DeadJobs: Lavori arrestati NoDeadJobsFound: Non ci sono lavori arrestati Dead: Arrestato Processes: Processi Thread: Thread Threads: Thread Jobs: Lavori sidekiq-4.0.1/web/locales/ja.yml0000644000175600017570000000372512631157272015537 0ustar pravipravi# elements like %{queue} are variables and should not be translated ja: Dashboard: ダッシュボード Status: 状態 Time: 時間 Namespace: ネームスペース Realtime: リアルタイム History: 履歴 Busy: ビジー Processed: 処理完了 Failed: 失敗 Scheduled: 予定 Retries: 再試行 Enqueued: 待機状態 Worker: 動作中の作業 LivePoll: ポーリング開始 StopPolling: ポーリング停止 Queue: キュー Class: クラス Job: ジョブ Arguments: 引数 Extras: エクストラ Started: 開始 ShowAll: 全て見せる CurrentMessagesInQueue: %{queue}に メッセージがあります Delete: 削除 AddToQueue: キューに追加 AreYouSureDeleteJob: このジョブを削除しますか? AreYouSureDeleteQueue: この %{queue} キューを削除しますか? Queues: キュー Size: サイズ Actions: アクション NextRetry: 再試行 RetryCount: 再試行 RetryNow: 今すぐ再試行 LastRetry: 再試行履歴 OriginallyFailed: 失敗 AreYouSure: いいですか? DeleteAll: 全て削除 RetryAll: 全て再試行 NoRetriesFound: 再試行できません Error: エラー ErrorClass: クラスエラー ErrorMessage: エラーメッセージ ErrorBacktrace: エラーバックトレース GoBack: ← 戻る NoScheduledFound: 予定したジョブはありません When: いつ ScheduledJobs: 予定したジョブ idle: アイドル active: アクティブ Version: バージョン Connections: 接続 MemoryUsage: メモリー容量 PeakMemoryUsage: 最大メモリー容量 Uptime: Uptime (days) OneWeek: 1 週 OneMonth: 1 ヶ月 ThreeMonths: 3 ヶ月 SixMonths: 6 ヶ月 Batches: バッチ Failures: 失敗 DeadJobs: 死亡したジョブ NoDeadJobsFound: 死亡したジョブはありません Dead: 死亡 Processes: プロセス Thread: スレッド Threads: スレッド Jobs: ジョブ sidekiq-4.0.1/web/locales/zh-cn.yml0000644000175600017570000000342512631157272016161 0ustar pravipravi# elements like %{queue} are variables and should not be translated zh-cn: # <---- change this to your locale code Dashboard: 信息板 Status: 状态 Time: 时间 Namespace: 命名空间 Realtime: 实时 History: 历史记录 Busy: 执行中 Processed: 已处理 Failed: 已失败 Scheduled: 已计划 Retries: 重试 Enqueued: 已进入队列 Worker: 工人 LivePoll: 实时轮询 StopPolling: 停止轮询 Queue: 队列 Class: 类别 Job: 作业 Arguments: 参数 Extras: 额外的 Started: 已开始 ShowAll: 显示全部 CurrentMessagesInQueue: 目前在%{queue}的作业 Delete: 删除 AddToQueue: 添加至队列 AreYouSureDeleteJob: 你确定要删除这个作业么? AreYouSureDeleteQueue: 你确定要删除%{queue}这个队列? Queues: 队列 Size: 容量 Actions: 动作 NextRetry: 下次重试 RetryCount: 重试次數 RetryNow: 现在重试 LastRetry: 最后一次重试 OriginallyFailed: 原本已失败 AreYouSure: 你确定? DeleteAll: 删除全部 RetryAll: 重试全部 NoRetriesFound: 沒有发现可重试 Error: 错误 ErrorClass: 错误类别 ErrorMessage: 错误消息 ErrorBacktrace: 错误的回调追踪 GoBack: ← 返回 NoScheduledFound: 沒有发现计划作业 When: 当 ScheduledJobs: 计划作业 idle: 闲置 active: 活动中 Version: 版本 Connections: 连接 MemoryUsage: 内存占用 PeakMemoryUsage: 内存占用峰值 Uptime: 上线时间 (天数) OneWeek: 一周 OneMonth: 一个月 ThreeMonths: 三个月 SixMonths: 六个月 Failures: 失败 DeadJobs: 已停滞作业 NoDeadJobsFound: 沒有发现任何已停滞的作业 Dead: 已停滞 Processes: 处理中 Thread: 线程 Threads: 线程 Jobs: 作业 sidekiq-4.0.1/web/locales/ta.yml0000644000175600017570000001020012631157272015533 0ustar pravipravi# elements like %{queue} are variables and should not be translated ta: # <---- change this to your locale code Dashboard: டாஷ்போர்டு Status: நிலைமை Time: நேரம் Namespace: பெயர்வெளி Realtime: நேரலை History: வரலாறு Busy: பணிமிகுதி Processed: நிறையுற்றது Failed: தோல்வி Scheduled: திட்டமிடப்பட்ட Retries: மீண்டும் முயற்சிக்க, Enqueued: வரிசைப்படுத்தப்பட்டவை Worker: பணியாளர் LivePoll: நேரடி கணிப்பு StopPolling: நிறுத்து வாக்குப்பதிவு Queue: வரிசை Class: வகுப்பு Job: வேலை Arguments: வாதங்கள், Extras: உபரி Started: தொடங்குதல் ShowAll: அனைத்து காட்டு CurrentMessagesInQueue: தற்போதைய வேலைகள் %{queue} Delete: நீக்கு AddToQueue: வரிசையில் சேர் AreYouSureDeleteJob: நீ இந்த வேலையை நீக்க வேண்டும் என்று உறுதியாக இருக்கிறீர்களா? AreYouSureDeleteQueue: நீங்கள் %{queue} வரிசையில் நீக்க வேண்டும் என்பதில் உறுதியாக இருக்கிறீர்களா? Queues: வரிசை Size: அளவு Actions: செயல்கள் NextRetry: அடுத்த, மீண்டும் முயற்சிக்கவும் RetryCount: கணிப்பீடு, மீண்டும் முயற்சிக்கவும் RetryNow: இப்போது மீண்டும் முயற்சி செய்க Kill: கொல் LastRetry: கடைசியாக, மீண்டும் முயற்சிக்கவும் OriginallyFailed: முதலில் தோல்வி AreYouSure: நீங்கள் உறுதியாக இருக்கிறீர்களா? DeleteAll: அனைத்து நீக்கு RetryAll: அனைத்து, மீண்டும் முயற்சிக்கவும் NoRetriesFound: இல்லை மீண்டும் காணப்படவில்லை Error: பிழை ErrorClass: பிழை வகுப்பு ErrorMessage: பிழை செய்தி ErrorBacktrace: பிழை பின்தேடுலை GoBack: பின்புறம் NoScheduledFound: திட்டமிட்ட வேலைகள் காணப்படவில்லை When: எப்பொழுது? ScheduledJobs: திட்டமிட்ட வேலைகள் idle: முடங்கு நேரம் active: செயலில் Version: பதிப்பு Connections: இணைப்புகள் MemoryUsage: நினைவக பயன்பாடு PeakMemoryUsage: உச்ச நினைவக பயன்பாடு Uptime: இயக்க நேரம் (நாட்கள்) OneWeek: 1 வாரம் OneMonth: 1 மாதம் ThreeMonths: 3 மாதங்கள் SixMonths: 6 மாதங்கள் Failures: தோல்விகள் DeadJobs: டெட் வேலைகள் NoDeadJobsFound: இறந்த வேலை எதுவும் இல்லை Dead: இறந்துபோன Processes: செயல்முறைகள் Thread: நூல் Threads: நூல்கள் Jobs: வேலை வாய்ப்புகள் Paused: தற்காலிக பணிநிறுத்தம் Stop: நிறுத்து Quiet: அமைதியான StopAll: நிறுத்து அனைத்து QuietAll: அமைதியான அனைத்து PollingInterval: வாக்குப்பதிவு இடைவெளிsidekiq-4.0.1/web/locales/nb.yml0000644000175600017570000000370112631157272015536 0ustar pravipravi# elements like %{queue} are variables and should not be translated nb: Dashboard: Oversikt Status: Status Time: Tid Namespace: Navnerom Realtime: Sanntid History: Historikk Busy: Opptatt Processed: Prosessert Failed: Mislykket Scheduled: Planlagt Retries: Forsøk Enqueued: I kø Worker: Arbeider LivePoll: Automatisk oppdatering StopPolling: Stopp automatisk oppdatering Queue: Kø Class: Klasse Job: Jobb Arguments: Argumenter Extras: Ekstra Started: Startet ShowAll: Vis alle CurrentMessagesInQueue: Nåværende melding i %{queue} Delete: Slett AddToQueue: Legg til i kø AreYouSureDeleteJob: Er du sikker på at du vil slette denne jobben? AreYouSureDeleteQueue: Er du sikker på at du vil slette køen %{queue}? Queues: Køer Size: Størrelse Actions: Handlinger NextRetry: Neste forsøk RetryCount: Antall forsøk RetryNow: Forsøk igjen nå Kill: Kill LastRetry: Forrige forsøk OriginallyFailed: Feilet opprinnelig AreYouSure: Er du sikker? DeleteAll: Slett alle RetryAll: Forsøk alle på nytt NoRetriesFound: Ingen forsøk funnet Error: Feil ErrorClass: Feilklasse ErrorMessage: Feilmelding ErrorBacktrace: Feilbakgrunn GoBack: ← Tilbake NoScheduledFound: Ingen planlagte jobber funnet When: Når ScheduledJobs: Planlagte jobber idle: uvirksom active: aktiv Version: Versjon Connections: Tilkoblinger MemoryUsage: Minneforbruk PeakMemoryUsage: Høyeste minneforbruk Uptime: Oppetid (dager) OneWeek: 1 uke OneMonth: 1 måned ThreeMonths: 3 måneder SixMonths: 6 måneder Failures: Feil DeadJobs: Døde jobber NoDeadJobsFound: Ingen døde jobber funnet Dead: Død Processes: Prosesser Thread: Tråd Threads: Tråder Jobs: Jobber Paused: Pauset Stop: Stopp Quiet: Demp StopAll: Stopp alle QuietAll: Demp alle PollingInterval: Oppdateringsintervall Plugins: Innstikk NotYetEnqueued: Ikke køet enda sidekiq-4.0.1/web/locales/pt.yml0000644000175600017570000000350512631157272015564 0ustar pravipravi# elements like %{queue} are variables and should not be translated pt: Dashboard: Dashboard Status: Estado Time: Tempo Namespace: Namespace Realtime: Tempo real History: Histórico Busy: Ocupado Processed: Processados Failed: Falhados Scheduled: Agendados Retries: Tentativas Enqueued: Em espera Worker: Worker LivePoll: Live Poll StopPolling: Desactivar Live Poll Queue: Fila Class: Classe Job: Tarefa Arguments: Argumentos Started: Iniciados ShowAll: Mostrar todos CurrentMessagesInQueue: Mensagens na fila %{queue} Delete: Apagar AddToQueue: Adicionar à fila AreYouSureDeleteJob: Tem a certeza que deseja eliminar esta tarefa? AreYouSureDeleteQueue: Tem a certeza que deseja eliminar a fila %{queue}? Queues: Filas Size: Tamanho Actions: Acções NextRetry: Próxima Tentativa RetryCount: Tentativas efectuadas RetryNow: Tentar novamente LastRetry: Última Tentativa OriginallyFailed: Falhou inicialmente AreYouSure: Tem a certeza? DeleteAll: Eliminar todos RetryAll: Tentar tudo novamente NoRetriesFound: Não foram encontradas tentativas Error: Erro ErrorClass: Classe de Erro ErrorMessage: Mensagem de erro ErrorBacktrace: Backtrace do Erro GoBack: ← Voltar NoScheduledFound: Não foram encontradas tarefas agendadas When: Quando ScheduledJobs: Tarefas agendadas idle: livre active: activo Version: Versão Connections: Conexões MemoryUsage: Utilização de Memória PeakMemoryUsage: Pico de utilização de memória Uptime: Uptime (em dias) OneWeek: 1 semana OneMonth: 1 mês ThreeMonths: 3 meses SixMonths: 6 meses Failures: Falhas DeadJobs: Tarefas mortas NoDeadJobsFound: Não foram encontradas tarefas mortas Dead: Morto Processes: Processos Thread: Thread Threads: Threads Jobs: Tarefas sidekiq-4.0.1/web/locales/es.yml0000644000175600017570000000353712631157272015555 0ustar pravipravi# elements like %{queue} are variables and should not be translated es: Dashboard: Panel de Control Status: Estatus Time: Tiempo Namespace: Espacio de Nombre Realtime: Tiempo Real History: Historial Busy: Ocupado Processed: Procesadas Failed: Fallidas Scheduled: Programadas Retries: Reintentos Enqueued: En Fila Worker: Trabajador LivePoll: Sondeo en Vivo StopPolling: Detener Sondeo Queue: Fila Class: Clase Job: Trabajo Arguments: Argumentos Extras: Extras Started: Hora de Inicio ShowAll: Mostrar Todo CurrentMessagesInQueue: Mensajes actualmente en %{queue} Delete: Eliminar AddToQueue: Añadir a fila AreYouSureDeleteJob: ¿Estás seguro de eliminar este trabajo? AreYouSureDeleteQueue: ¿Estás seguro de eliminar la fila %{queue}? Queues: Filas Size: Tamaño Actions: Acciones NextRetry: Siguiente Intento RetryCount: Numero de Reintentos RetryNow: Reintentar Ahora Kill: Matar LastRetry: Último Reintento OriginallyFailed: Falló Originalmente AreYouSure: ¿Estás seguro? DeleteAll: Borrar Todo RetryAll: Reintentar Todo NoRetriesFound: No se encontraron reintentos Error: Error ErrorClass: Clase del Error ErrorMessage: Mensaje de Error ErrorBacktrace: Trazado del Error GoBack: ← Regresar NoScheduledFound: No se encontraron trabajos pendientes When: Cuando ScheduledJobs: Trabajos programados idle: inactivo active: activo Version: Versión Connections: Conexiones MemoryUsage: Uso de Memoria PeakMemoryUsage: Máximo Uso de Memoria Uptime: Tiempo de Funcionamiento (días) OneWeek: 1 semana OneMonth: 1 mes ThreeMonths: 3 meses SixMonths: 6 meses Failures: Fallas DeadJobs: Trabajos muertos NoDeadJobsFound: No hay trabajos muertos Dead: Muerto Processes: Procesos Thread: Hilo Threads: Hilos Jobs: Trabajos sidekiq-4.0.1/web/locales/de.yml0000644000175600017570000000352712631157272015535 0ustar pravipravi# elements like %{queue} are variables and should not be translated de: Dashboard: Dashboard Status: Status Time: Zeit Namespace: Namensraum Realtime: Echtzeit History: Verlauf Busy: Beschäftigt Processed: Verarbeitet Failed: Fehlgeschlagen Scheduled: Geplant Retries: Versuche Enqueued: In der Warteschlange Worker: Arbeiter LivePoll: Live Poll StopPolling: Abfrage stoppen Queue: Warteschlange Class: Klasse Job: Job Extras: Extras Arguments: Argumente Started: Gestartet ShowAll: Alle anzeigen CurrentMessagesInQueue: Aktuelle Nachrichten in %{queue} Delete: Löschen AddToQueue: In Warteschlange einreihen AreYouSureDeleteJob: Möchtest du diesen Job wirklich löschen? AreYouSureDeleteQueue: Möchtest du %{queue} wirklich löschen? Queues: Warteschlangen Size: Größe Actions: Aktionen NextRetry: Nächster Versuch RetryCount: Anzahl der Versuche RetryNow: Jetzt erneut versuchen Kill: Töten LastRetry: Letzter Versuch OriginallyFailed: Ursprünglich fehlgeschlagen AreYouSure: Bist du sicher? DeleteAll: Alle löschen RetryAll: Alle erneut versuchen NoRetriesFound: Keine erneuten Versuche gefunden Error: Fehler ErrorClass: Fehlerklasse ErrorMessage: Fehlernachricht ErrorBacktrace: Fehlerbericht GoBack: ← Zurück NoScheduledFound: Keine geplanten Jobs gefunden When: Wann ScheduledJobs: Jobs in der Warteschlange idle: inaktiv active: aktiv Version: Version Connections: Verbindungen MemoryUsage: RAM-Nutzung PeakMemoryUsage: Maximale RAM-Nutzung Uptime: Laufzeit OneWeek: 1 Woche OneMonth: 1 Monat ThreeMonths: 3 Monate SixMonths: 6 Monate Failures: Ausfälle DeadJobs: Gestorbene Jobs NoDeadJobsFound: Keine toten Jobs gefunden Dead: Tot Processes: Prozesse Thread: Thread Threads: Threads Jobs: Jobs sidekiq-4.0.1/web/locales/sv.yml0000644000175600017570000000342312631157272015570 0ustar pravipravi# elements like %{queue} are variables and should not be translated sv: # <---- change this to your locale code Dashboard: Panel Status: Status Time: Tid Namespace: Namnrymd Realtime: Realtid History: Historik Busy: Upptagen Processed: Processerad Failed: Misslyckad Scheduled: Schemalagd Retries: Försök Enqueued: Köad Worker: Worker LivePoll: Live poll StopPolling: Stoppa polling Queue: Kö Class: Klass Job: Jobb Arguments: Argument Extras: Extra Started: Startad ShowAll: Visa alla CurrentMessagesInQueue: Jobb i %{queue} Delete: Ta bort AddToQueue: Lägg till i kö AreYouSureDeleteJob: Är du säker på att du vill ta bort detta jobb? AreYouSureDeleteQueue: Är du säker på att du vill ta bort kön %{queue}? Queues: Köer Size: Storlek Actions: Åtgärder NextRetry: Nästa försök RetryCount: Antal försök RetryNow: Försök nu LastRetry: Senaste försök OriginallyFailed: Misslyckades ursprungligen AreYouSure: Är du säker? DeleteAll: Ta bort alla RetryAll: Försök alla igen NoRetriesFound: Inga försök hittades Error: Fel ErrorClass: Felklass ErrorMessage: Felmeddelande ErrorBacktrace: Backtrace för fel GoBack: ← Bakåt NoScheduledFound: Inga schemalagda jobb hittades When: När ScheduledJobs: Schemalagda jobb idle: avvaktande active: aktiv Version: Version Connections: Anslutningar MemoryUsage: Minnesanvändning PeakMemoryUsage: Minnesanvändning (peak) Uptime: Upptid (dagar) OneWeek: 1 vecka OneMonth: 1 månad ThreeMonths: 3 månader SixMonths: 6 månader Failures: Failures DeadJobs: Döda jobb NoDeadJobsFound: Inga döda jobb hittades Dead: Död Processes: Processer Thread: Tråd Threads: Trådar Jobs: Jobb sidekiq-4.0.1/web/locales/da.yml0000644000175600017570000000327312631157272015527 0ustar pravipravi# elements like %{queue} are variables and should not be translated da: Dashboard: Instrumentbræt Status: Status Time: Tid Namespace: Namespace Realtime: Real-time History: Historik Busy: Travl Processed: Processeret Failed: Fejlet Scheduled: Planlagt Retries: Forsøg Enqueued: I kø Worker: Arbejder LivePoll: Live Poll StopPolling: Stop Polling Queue: Kø Class: Klasse Job: Job Arguments: Argumenter Extras: Ekstra Started: Startet ShowAll: Vis alle CurrentMessagesInQueue: Nuværende beskeder i %{queue} Delete: Slet AddToQueue: Tilføj til kø AreYouSureDeleteJob: Er du sikker på at du vil slette dette job? AreYouSureDeleteQueue: Er du sikker på at du vil slette %{queue} køen? Queues: Køer Size: Størrelse Actions: Actions NextRetry: Næste forsøg RetryCount: Antal forsøg RetryNow: Prøv igen nu LastRetry: Sidste forsøg OriginallyFailed: Oprindeligt fejlet AreYouSure: Er du sikker? DeleteAll: Slet alle RetryAll: Forsøg alle NoRetriesFound: Ingen gen-forsøg var fundet Error: Fejl ErrorClass: Fejl klasse ErrorMessage: Fejl besked ErrorBacktrace: Fejl backtrace GoBack: ← Tilbage NoScheduledFound: Ingen jobs i kø fundet When: Når ScheduledJobs: Jobs i kø idle: idle active: aktiv Version: Version Connections: Forbindelser MemoryUsage: RAM forbrug PeakMemoryUsage: Peak RAM forbrug Uptime: Oppetid (dage) OneWeek: 1 uge OneMonth: 1 måned ThreeMonths: 3 måneder SixMonths: 6 måneder Failures: Fejl DeadJobs: Døde jobs NoDeadJobsFound: Ingen døde jobs fundet Dead: Død Processes: Processer Thread: Tråd Threads: Tråde Jobs: Jobs sidekiq-4.0.1/web/locales/en.yml0000644000175600017570000000360112631157272015540 0ustar pravipravi# elements like %{queue} are variables and should not be translated en: # <---- change this to your locale code Dashboard: Dashboard Status: Status Time: Time Namespace: Namespace Realtime: Real-time History: History Busy: Busy Processed: Processed Failed: Failed Scheduled: Scheduled Retries: Retries Enqueued: Enqueued Worker: Worker LivePoll: Live Poll StopPolling: Stop Polling Queue: Queue Class: Class Job: Job Arguments: Arguments Extras: Extras Started: Started ShowAll: Show All CurrentMessagesInQueue: Current jobs in %{queue} Delete: Delete AddToQueue: Add to queue AreYouSureDeleteJob: Are you sure you want to delete this job? AreYouSureDeleteQueue: Are you sure you want to delete the %{queue} queue? Queues: Queues Size: Size Actions: Actions NextRetry: Next Retry RetryCount: Retry Count RetryNow: Retry Now Kill: Kill LastRetry: Last Retry OriginallyFailed: Originally Failed AreYouSure: Are you sure? DeleteAll: Delete All RetryAll: Retry All NoRetriesFound: No retries were found Error: Error ErrorClass: Error Class ErrorMessage: Error Message ErrorBacktrace: Error Backtrace GoBack: ← Back NoScheduledFound: No scheduled jobs were found When: When ScheduledJobs: Scheduled Jobs idle: idle active: active Version: Version Connections: Connections MemoryUsage: Memory Usage PeakMemoryUsage: Peak Memory Usage Uptime: Uptime (days) OneWeek: 1 week OneMonth: 1 month ThreeMonths: 3 months SixMonths: 6 months Failures: Failures DeadJobs: Dead Jobs NoDeadJobsFound: No dead jobs were found Dead: Dead Processes: Processes Thread: Thread Threads: Threads Jobs: Jobs Paused: Paused Stop: Stop Quiet: Quiet StopAll: Stop All QuietAll: Quiet All PollingInterval: Polling interval Plugins: Plugins NotYetEnqueued: Not yet enqueued sidekiq-4.0.1/web/locales/cs.yml0000644000175600017570000000365412631157272015553 0ustar pravipravi# elements like %{queue} are variables and should not be translated cs: Dashboard: Kontrolní panel Status: Stav Time: Čas Namespace: Jmenný prostor Realtime: V reálném čase History: Historie Busy: Zaneprázdněné Processed: Zpracované Failed: Nezdařené Scheduled: Naplánované Retries: Opakování Enqueued: Zařazené Worker: Worker LivePoll: Průběžně aktualizovat StopPolling: Zastavit průběžnou aktualizaci Queue: Fronta Class: Třída Job: Úkol Arguments: Argumenty Extras: Doplňky Started: Začal ShowAll: Ukázat všechny CurrentMessagesInQueue: Aktuální úkoly ve frontě %{queue} Delete: Odstranit AddToQueue: Přidat do fronty AreYouSureDeleteJob: Jste si jisti, že chcete odstranit tento úkol? AreYouSureDeleteQueue: Jste si jisti, že chcete odstranit frontu %{queue}? Queues: Fronty Size: Velikost Actions: Akce NextRetry: Další opakování RetryCount: Počet opakování RetryNow: Opakovat teď LastRetry: Poslední opakování OriginallyFailed: Původně se nezdařilo AreYouSure: Jste si jisti? DeleteAll: Odstranit vše RetryAll: Opakovat vše NoRetriesFound: Nebyla nalezena žádná opakování Error: Chyba ErrorClass: Třída chyby ErrorMessage: Chybová zpráva ErrorBacktrace: Chybový výstup GoBack: ← Zpět NoScheduledFound: Nebyly nalezeny žádné naplánované úkoly When: Kdy ScheduledJobs: Naplánované úkoly idle: nečinný active: aktivní Version: Verze Connections: Připojení MemoryUsage: Využití paměti PeakMemoryUsage: Nejvyšší využití paměti Uptime: Uptime (dny) OneWeek: 1 týden OneMonth: 1 měsíc ThreeMonths: 3 měsíce SixMonths: 6 měsíců Failures: Selhání DeadJobs: Mrtvé úkoly NoDeadJobsFound: Nebyly nalezeny žádné mrtvé úkoly Dead: Mrtvý Processes: Procesy Thread: Vlákno Threads: Vlákna Jobs: Úkoly sidekiq-4.0.1/web/assets/0000755000175600017570000000000012631157272014273 5ustar pravipravisidekiq-4.0.1/web/assets/stylesheets/0000755000175600017570000000000012631157272016647 5ustar pravipravisidekiq-4.0.1/web/assets/stylesheets/application.css0000755000175600017570000011560712631157272021701 0ustar pravipravi* { box-sizing: border-box; } body { color: #585454; padding: 0; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; background: #f3f3f3 url(data:image/png;base64,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); } a { color: #b1003e; } a:active, a:hover, a:visited { color: #4b001a; } a.btn { color: #585454; } a.btn-primary { color: white; } h1, h2, h3, h4, h5, h6, strong { font-weight: 700; } .navbar-brand, .navbar .navbar-brand, h5, h4, h3, h2, h1 { font-family: Armata, "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 400; } .title { color: #b1003e; } pre { font-size: 11px; } code { background: none; border: none; } section { padding-top: 10px; } code { padding: 0; } footer { padding: 40px 20px; text-align: center; } footer .edits { margin-right: 40px; } body { padding: 0 20px; } h3 { line-height: 45px; } .centered { text-align: center; } .admin #page { padding: 60px 0; } header.row .pagination { margin: 12px 0; } .summary_bar .status { margin-left: 10px; } .summary_bar .summary { margin-top: 12px; background-color: #fff; -webkit-box-shadow: 0 0 5px rgba(50, 50, 50, 0.25); -moz-box-shadow: 0 0 5px rgba(50, 50, 50, 0.25); box-shadow: 0 0 5px rgba(50, 50, 50, 0.25); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; padding: 8px; margin-bottom: 10px; border-width: 0; } .poll-wrapper { margin: 9px; } #live-poll.active { background-color: #009300; } .summary_bar ul { margin: 0 0 38px 0; } .summary_bar ul h3 { font-size: 1em; margin: 0; font-weight: normal; line-height: 1em; } .summary_bar ul li { padding: 4px 0 2px 0; text-align: center; width: 14%; } @media (max-width: 767px) and (min-width: 400px) { .summary_bar ul li { width: 100%; } .summary_bar ul li span { width: 50% !important; } .summary_bar ul .desc { text-align: left; } .summary_bar ul .count { text-align: right; } } @media (max-width: 979px) and (min-width: 768px) { .summary_bar ul li.col-sm-2 { margin: 0 10px; width: 96px !important; } } /*@media (max-width: 1199px) and (min-width: 980px) { .summary_bar ul li.col-sm-2 { width: 130px !important; } } @media (min-width: 1200px) { .summary_bar ul li.col-sm-2 { width: 154px !important; } }*/ .summary_bar ul .desc { display: block; font-size: 1em; font-weight: normal; width: 100%; } .summary_bar ul .count { color: #b1003e; display: block; font-size: 1em; font-weight: bold; float: right; padding: 0 0 2px 0; width: 100%; } .table_container { overflow: overlay; } table.table-white { background-color: #fff; } .queues form { margin: 0; } form .btn { margin-right: 5px; } form .btn-group .btn { margin-right: 1px; } td form { margin-bottom: 0; } .table tr > td.table-checkbox, .table tr > th.table-checkbox { padding: 0; } table .table-checkbox label { height: 100%; width: 100%; padding: 0 16px; margin-bottom: 0; line-height: 32px; } .navbar .navbar-brand { color: #b1003e; padding: 13px; text-shadow: none; } .navbar .navbar-brand .status { color: #585454; } @media (max-width: 768px) { .navbar .navbar-header .navbar-livereload { border: none; margin: 9px 10px 0; padding: 0; } .navbar .navbar-collapse { max-height: 400px; } .navbar .navbar-collapse .navbar-livereload { display: none; } .navbar.navbar-fixed-top ul { margin-right: -15px!important; } .navbar .nav a { text-align: center; } } @media (width: 768px) { .navbar .navbar-collapse .navbar-livereload { display: block; margin-top: 5px; } .navbar .poll-wrapper { margin: 4px 4px 0 0; } .navbar .dropdown-menu { min-width: 120px; } .navbar .dropdown-menu a { text-align: left; } } .nav .dropdown { display: none; } .navbar-footer .navbar ul.nav { text-align: center; float: none; } .navbar-footer .navbar ul.nav a { font-weight: 700; font-size: 16px; padding: 15px; } .navbar-footer .navbar ul.nav a.navbar-brand { font-weight: 400; padding: 0 15px 0 0; } .navbar-footer .navbar ul.nav li { display: inline-block; float: none; } .navbar-footer .navbar.affix { top: 0; width: 100%; z-index: 10; } img.smallogo { width: 30px; margin: 0 0 6px 0; } .navbar-fixed-bottom li { margin-right: 20px; } .status-sprite { background-image: url(../images/status-sd8051fd480.png); height: 19px; width: 20px; display: inline-block; background-size: 20px; } .status-active { background-position: 0 0; } .status-idle { background-position: 0 -54px; } .btn { font-weight: 700; border: none; -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.4); -moz-box-shadow: 0 0 2px rgba(0, 0, 0, 0.4); box-shadow: 0 0 2px rgba(0, 0, 0, 0.4); background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fafafa), color-stop(100%, #ededed)); background-image: -webkit-linear-gradient(#fafafa, #ededed); background-image: -moz-linear-gradient(#fafafa, #ededed); background-image: -o-linear-gradient(#fafafa, #ededed); background-image: linear-gradient(#fafafa, #ededed); } .btn:hover { background-color: #ededed; } .btn-primary { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b1003e), color-stop(100%, #980035)); background-image: -webkit-linear-gradient(#b1003e, #980035); background-image: -moz-linear-gradient(#b1003e, #980035); background-image: -o-linear-gradient(#b1003e, #980035); background-image: linear-gradient(#b1003e, #980035); } .btn-primary:hover { background-color: #980035; } .btn-danger { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b1003e), color-stop(100%, #980035)); background-image: -webkit-linear-gradient(#b1003e, #980035); background-image: -moz-linear-gradient(#b1003e, #980035); background-image: -o-linear-gradient(#b1003e, #980035); background-image: linear-gradient(#b1003e, #980035); } .btn-danger:hover { background-color: #980035; } .poll-status { padding: 10px 0; } .redis-wrapper { width: 100%; text-align: center; } .stats-container { display: inline-block; } .stat { float: left; text-align: center; margin-right: 20px; border: 1px solid rgba(0, 0, 0, 0.1); padding: 5px; width: 150px; margin-bottom: 20px; } .stat:last-child { margin-right: 0; } .stat p { font-size: 0.9em; } @media (max-width: 767px) { .stats-container { display: block; } .stat { float: left; margin-right: 10px; width: 100%; text-align: left; line-height: 45px; } .stat h3{ float: right; margin: 5px 10px 5px 5px; } .stat p{ font-size: 1em; margin: 5px 5px 5px 10px; } } /* Dashboard ********************************** */ div.dashboard h3 { float: left; } div.interval-slider { float: right; line-height: 1.3; font-size: 0.95em; padding: 15px 0 0; } span.current-interval { min-width: 40px; display: inline-block; padding: 0 0 5px 0; color: #B1003E; } div.interval-slider input { width: 160px; } #realtime-legend, #history-legend { width: 580px; text-align: left; margin-top: 5px; float: right; } #realtime-legend .timestamp, #history-legend .timestamp { display: inline-block; width: 220px; text-align: right; } #realtime-legend .line, #history-legend .line { display: inline-block; margin: 0 0 0 20px; } #realtime-legend .swatch, #history-legend .swatch { display: inline-block; width: 10px; height: 10px; margin: 0 8px 0 0; } #realtime-legend .tag, #history-legend .tag { display: inline-block; } @media (max-width: 790px) { #realtime-legend, #history-legend { float: none; width: 100%; margin-bottom: 20px; } } @media (max-width: 500px) { #realtime-legend, #history-legend { text-align: center; } } /* Beacon ********************************** */ .beacon { position: relative; width: 20px; height: 20px; display: inline-block; } .beacon .dot, .beacon .ring { position: absolute; top: 50%; left: 50%; } .beacon .dot { width: 10px; height: 10px; margin: -5px 0 0 -5px; background-color: #80002d; border-radius: 10px; box-shadow: 0 0 9px #666; -moz-box-shadow: 0 0 9px #666; -webkit-box-shadow: 0 0 9px #666; border: 3px solid transparent; z-index: 10; } .beacon.pulse .dot { -webkit-animation: beacon-dot-pulse 1s ease-out; -moz-animation: beacon-dot-pulse 1s ease-out; animation: beacon-dot-pulse 1s ease-out; } @-webkit-keyframes beacon-dot-pulse { from { background-color: #80002d; -webkit-box-shadow: 0 0 9px #666; } 50% { background-color: #c90047; -webkit-box-shadow: 0 0 18px #666; } to { background-color: #80002d; -webkit-box-shadow: 0 0 9px #666; } } @-moz-keyframes beacon-dot-pulse { from { background-color: #80002d; -moz-box-shadow: 0 0 9px #666; } 50% { background-color: #c90047; -moz-box-shadow: 0 0 18px #666; } to { background-color: #80002d; -moz-box-shadow: 0 0 9px #666; } } @keyframes beacon-dot-pulse { from { background-color: #80002d; box-shadow: 0 0 9px #666; } 50% { background-color: #c90047; box-shadow: 0 0 18px #666; } to { background-color: #80002d; box-shadow: 0 0 9px #666; } } .beacon .ring { width: 28px; height: 28px; margin: -14px 0 0 -14px; border-radius: 28px; border: 3px solid #80002d; z-index: 5; opacity: 0; } .beacon.pulse .ring { -webkit-animation: beacon-ring-pulse 1s; -moz-animation: beacon-ring-pulse 1s; animation: beacon-ring-pulse 1s; } @-webkit-keyframes beacon-ring-pulse { 0% { opacity: 1; -webkit-transform: scale(0.3); } 100% { opacity: 0; -webkit-transform: scale(1); } } @-moz-keyframes beacon-ring-pulse { 0% { opacity: 1; -moz-transform: scale(0.3); } 100% { opacity: 0; -moz-transform: scale(1); } } @keyframes beacon-ring-pulse { 0% { opacity: 1; transform: scale(0.3); } 100% { opacity: 0; transform: scale(1); } } .chart { margin: 0; } .history-heading { padding-right: 15px; } .history-graph { font-size: 0.8em; padding: 3px; border-radius: 3px; } .history-graph.active { background-color: #B1003E; color: white; } .history-graph.active:hover { text-decoration: none; } @media (max-width: 767px) { .navbar .navbar-brand { float: none; display: block; } .navbar.navbar-fixed-top ul { margin-right: 0; } .navbar.navbar-fixed-top li { margin-right: 0; } .poll-wrapper { width: 100%; text-align: center; } .poll-wrapper > a { display: inline-block; margin: 5px; } .navbar.navbar-fixed-bottom ul { float: none; margin-right: 0; } .navbar.navbar-fixed-bottom li { float: none; margin-right: 0; } .navbar-text { float:none; line-height: 30px; margin: 15px auto; } } @media (max-width: 767px) { .navbar-fixed-top, .navbar-fixed-bottom { margin: 0 -20px; } .navbar ul.nav li a { padding-left: 8px; padding-right: 8px; } .admin #page { padding-top: 10px; } } @media (max-width: 500px) { .navbar-footer .navbar ul.nav a.navbar-brand { padding-right: 5px; } } /* Rickshaw */ .rickshaw_graph .detail{pointer-events:none;position:absolute;top:0;z-index:2;background:rgba(0,0,0,.1);bottom:0;width:1px;transition:opacity .25s linear;-moz-transition:opacity .25s linear;-o-transition:opacity .25s linear;-webkit-transition:opacity .25s linear}.rickshaw_graph .detail.inactive{opacity:0}.rickshaw_graph .detail .item.active{opacity:1}.rickshaw_graph .detail .x_label{font-family:Arial,sans-serif;border-radius:3px;padding:6px;opacity:.5;border:1px solid #e0e0e0;font-size:12px;position:absolute;background:#fff;white-space:nowrap}.rickshaw_graph .detail .x_label.left{left:0}.rickshaw_graph .detail .x_label.right{right:0}.rickshaw_graph .detail .item{position:absolute;z-index:2;border-radius:3px;padding:.25em;font-size:12px;font-family:Arial,sans-serif;opacity:0;background:rgba(0,0,0,.4);color:#fff;border:1px solid rgba(0,0,0,.4);margin-left:1em;margin-right:1em;margin-top:-1em;white-space:nowrap}.rickshaw_graph .detail .item.left{left:0}.rickshaw_graph .detail .item.right{right:0}.rickshaw_graph .detail .item.active{opacity:1;background:rgba(0,0,0,.8)}.rickshaw_graph .detail .item:after{position:absolute;display:block;width:0;height:0;content:"";border:5px solid transparent}.rickshaw_graph .detail .item.left:after{top:1em;left:-5px;margin-top:-5px;border-right-color:rgba(0,0,0,.8);border-left-width:0}.rickshaw_graph .detail .item.right:after{top:1em;right:-5px;margin-top:-5px;border-left-color:rgba(0,0,0,.8);border-right-width:0}.rickshaw_graph .detail .dot{width:4px;height:4px;margin-left:-3px;margin-top:-3.5px;border-radius:5px;position:absolute;box-shadow:0 0 2px rgba(0,0,0,.6);box-sizing:content-box;-moz-box-sizing:content-box;background:#fff;border-width:2px;border-style:solid;display:none;background-clip:padding-box}.rickshaw_graph .detail .dot.active{display:block}.rickshaw_graph{position:relative}.rickshaw_graph svg{display:block;overflow:hidden}.rickshaw_graph .x_tick{position:absolute;top:0;bottom:0;width:0;border-left:1px dotted rgba(0,0,0,.2);pointer-events:none}.rickshaw_graph .x_tick .title{position:absolute;font-size:12px;font-family:Arial,sans-serif;opacity:.5;white-space:nowrap;margin-left:3px;bottom:1px}.rickshaw_annotation_timeline{height:1px;border-top:1px solid #e0e0e0;margin-top:10px;position:relative}.rickshaw_annotation_timeline .annotation{position:absolute;height:6px;width:6px;margin-left:-2px;top:-3px;border-radius:5px;background-color:rgba(0,0,0,.25)}.rickshaw_graph .annotation_line{position:absolute;top:0;bottom:-6px;width:0;border-left:2px solid rgba(0,0,0,.3);display:none}.rickshaw_graph .annotation_line.active{display:block}.rickshaw_graph .annotation_range{background:rgba(0,0,0,.1);display:none;position:absolute;top:0;bottom:-6px}.rickshaw_graph .annotation_range.active{display:block}.rickshaw_graph .annotation_range.active.offscreen{display:none}.rickshaw_annotation_timeline .annotation .content{background:#fff;color:#000;opacity:.9;padding:5px;box-shadow:0 0 2px rgba(0,0,0,.8);border-radius:3px;position:relative;z-index:20;font-size:12px;padding:6px 8px 8px;top:18px;left:-11px;width:160px;display:none;cursor:pointer}.rickshaw_annotation_timeline .annotation .content:before{content:"\25b2";position:absolute;top:-11px;color:#fff;text-shadow:0 -1px 1px rgba(0,0,0,.8)}.rickshaw_annotation_timeline .annotation.active,.rickshaw_annotation_timeline .annotation:hover{background-color:rgba(0,0,0,.8);cursor:none}.rickshaw_annotation_timeline .annotation .content:hover{z-index:50}.rickshaw_annotation_timeline .annotation.active .content{display:block}.rickshaw_annotation_timeline .annotation:hover .content{display:block;z-index:50}.rickshaw_graph .y_axis,.rickshaw_graph .x_axis_d3{fill:none}.rickshaw_graph .y_ticks .tick line,.rickshaw_graph .x_ticks_d3 .tick{stroke:rgba(0,0,0,.16);stroke-width:2px;shape-rendering:crisp-edges;pointer-events:none}.rickshaw_graph .y_grid .tick,.rickshaw_graph .x_grid_d3 .tick{z-index:-1;stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:1 1}.rickshaw_graph .y_grid .tick[data-y-value="0"]{stroke-dasharray:1 0}.rickshaw_graph .y_grid path,.rickshaw_graph .x_grid_d3 path{fill:none;stroke:none}.rickshaw_graph .y_ticks path,.rickshaw_graph .x_ticks_d3 path{fill:none;stroke:gray}.rickshaw_graph .y_ticks text,.rickshaw_graph .x_ticks_d3 text{opacity:.5;font-size:12px;pointer-events:none}.rickshaw_graph .x_tick.glow .title,.rickshaw_graph .y_ticks.glow text{fill:#000;color:#000;text-shadow:-1px 1px 0 rgba(255,255,255,.1),1px -1px 0 rgba(255,255,255,.1),1px 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1),0 -1px 0 rgba(255,255,255,.1),1px 0 0 rgba(255,255,255,.1),-1px 0 0 rgba(255,255,255,.1),-1px -1px 0 rgba(255,255,255,.1)}.rickshaw_graph .x_tick.inverse .title,.rickshaw_graph .y_ticks.inverse text{fill:#fff;color:#fff;text-shadow:-1px 1px 0 rgba(0,0,0,.8),1px -1px 0 rgba(0,0,0,.8),1px 1px 0 rgba(0,0,0,.8),0 1px 0 rgba(0,0,0,.8),0 -1px 0 rgba(0,0,0,.8),1px 0 0 rgba(0,0,0,.8),-1px 0 0 rgba(0,0,0,.8),-1px -1px 0 rgba(0,0,0,.8)}.rickshaw_legend{font-family:Arial;font-size:12px;color:#fff;background:#404040;display:inline-block;padding:12px 5px;border-radius:2px;position:relative}.rickshaw_legend:hover{z-index:10}.rickshaw_legend .swatch{width:10px;height:10px;border:1px solid rgba(0,0,0,.2)}.rickshaw_legend .line{clear:both;line-height:140%;padding-right:15px}.rickshaw_legend .line .swatch{display:inline-block;margin-right:3px;border-radius:2px}.rickshaw_legend .label{margin:0;white-space:nowrap;display:inline;font-size:inherit;background-color:transparent;color:inherit;font-weight:400;line-height:normal;padding:0;text-shadow:none}.rickshaw_legend .action:hover{opacity:.6}.rickshaw_legend .action{margin-right:.2em;font-size:10px;opacity:.2;cursor:pointer;font-size:14px}.rickshaw_legend .line.disabled{opacity:.4}.rickshaw_legend ul{list-style-type:none;margin:0;padding:0;margin:2px;cursor:pointer}.rickshaw_legend li{padding:0 0 0 2px;min-width:80px;white-space:nowrap}.rickshaw_legend li:hover{background:rgba(255,255,255,.08);border-radius:3px}.rickshaw_legend li:active{background:rgba(255,255,255,.2);border-radius:3px} .code-wrap { white-space: normal; } .args { overflow-y: auto; max-height: 100px; word-break: break-all; } .args-extended { overflow-y: scroll; max-height: 500px; word-break: break-all; } /* BOOTSTRAP 3 FIXES */ /* @grid-float-breakpoint -1 */ .container { padding: 0; } @media (max-width: 767px) { .navbar-fixed-top, .navbar-fixed-bottom { position: relative; top: auto; } } .redis-url, .redis-namespace { overflow: hidden; white-space: nowrap; } @media (min-width: 768px) { .redis-url { max-width: 200px; } .redis-namespace { max-width: 150px; } } @media (min-width: 992px) { .redis-url { max-width: 390px; } .redis-namespace { max-width: 300px; } } @media (min-width: 1200px) { .redis-url { max-width: 480px; } .redis-namespace { max-width: 350px; } } .redis-url { text-overflow: ellipsis; } sidekiq-4.0.1/web/assets/stylesheets/bootstrap.css0000755000175600017570000027607312631157272021420 0ustar pravipravi/*! * Bootstrap v3.0.0 * * Copyright 2013 Twitter, Inc * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * * Designed and built with all the love in the world by @mdo and @fat. *//*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}button,input,select[multiple],textarea{background-image:none}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h4,h5,h6{margin-top:10px;margin-bottom:10px}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}h1 small,.h1 small{font-size:24px}h2 small,.h2 small{font-size:18px}h3 small,.h3 small,h4 small,.h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.428571429;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429}code,pre{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11{float:left}.col-xs-1{width:8.333333333333332%}.col-xs-2{width:16.666666666666664%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333333333%}.col-xs-5{width:41.66666666666667%}.col-xs-6{width:50%}.col-xs-7{width:58.333333333333336%}.col-xs-8{width:66.66666666666666%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333333334%}.col-xs-11{width:91.66666666666666%}.col-xs-12{width:100%}@media(min-width:768px){.container{max-width:750px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11{float:left}.col-sm-1{width:8.333333333333332%}.col-sm-2{width:16.666666666666664%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333333333%}.col-sm-5{width:41.66666666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333333333336%}.col-sm-8{width:66.66666666666666%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333333334%}.col-sm-11{width:91.66666666666666%}.col-sm-12{width:100%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-11{left:91.66666666666666%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-11{margin-left:91.66666666666666%}}@media(min-width:992px){.container{max-width:970px}.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11{float:left}.col-md-1{width:8.333333333333332%}.col-md-2{width:16.666666666666664%}.col-md-3{width:25%}.col-md-4{width:33.33333333333333%}.col-md-5{width:41.66666666666667%}.col-md-6{width:50%}.col-md-7{width:58.333333333333336%}.col-md-8{width:66.66666666666666%}.col-md-9{width:75%}.col-md-10{width:83.33333333333334%}.col-md-11{width:91.66666666666666%}.col-md-12{width:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.333333333333332%}.col-md-push-2{left:16.666666666666664%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333333333%}.col-md-push-5{left:41.66666666666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.333333333333336%}.col-md-push-8{left:66.66666666666666%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333333334%}.col-md-push-11{left:91.66666666666666%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-11{right:91.66666666666666%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-11{margin-left:91.66666666666666%}}@media(min-width:1200px){.container{max-width:1170px}.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11{float:left}.col-lg-1{width:8.333333333333332%}.col-lg-2{width:16.666666666666664%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333333333%}.col-lg-5{width:41.66666666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333333333336%}.col-lg-8{width:66.66666666666666%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333333334%}.col-lg-11{width:91.66666666666666%}.col-lg-12{width:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-11{left:91.66666666666666%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-11{margin-left:91.66666666666666%}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed thead>tr>th,.table-condensed tbody>tr>th,.table-condensed tfoot>tr>th,.table-condensed thead>tr>td,.table-condensed tbody>tr>td,.table-condensed tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8;border-color:#d6e9c6}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td{background-color:#d0e9c6;border-color:#c9e2b3}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede;border-color:#eed3d7}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td{background-color:#ebcccc;border-color:#e6c1c7}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3;border-color:#fbeed5}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td{background-color:#faf2cc;border-color:#f8e5be}@media(max-width:768px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0;background-color:#fff}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>thead>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>thead>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:normal;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm{height:auto}.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:45px;line-height:45px}textarea.input-lg{height:auto}.has-warning .help-block,.has-warning .control-label{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .help-block,.has-error .control-label{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .help-block,.has-success .control-label{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.form-control-static{padding-top:7px;margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline .radio,.form-inline .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-link{font-weight:normal;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:normal;line-height:1}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-print:before{content:"\e045"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-briefcase:before{content:"\1f4bc"}.glyphicon-calendar:before{content:"\1f4c5"}.glyphicon-pushpin:before{content:"\1f4cc"}.glyphicon-paperclip:before{content:"\1f4ce"}.glyphicon-camera:before{content:"\1f4f7"}.glyphicon-lock:before{content:"\1f512"}.glyphicon-bell:before{content:"\1f514"}.glyphicon-bookmark:before{content:"\1f516"}.glyphicon-fire:before{content:"\1f525"}.glyphicon-wrench:before{content:"\1f527"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid #000;border-right:4px solid transparent;border-bottom:0 dotted;border-left:4px solid transparent;content:""}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#fff;text-decoration:none;background-color:#428bca}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0 dotted;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.btn-default .caret{border-top-color:#333}.btn-primary .caret,.btn-success .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret{border-top-color:#fff}.dropup .btn-default .caret{border-bottom-color:#333}.dropup .btn-primary .caret,.dropup .btn-success .caret,.dropup .btn-warning .caret,.dropup .btn-danger .caret,.dropup .btn-info .caret{border-bottom-color:#fff}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:5px 10px;padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified .btn{display:table-cell;float:none;width:1%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group.col{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:45px;line-height:45px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}}.nav-tabs.nav-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs.nav-justified>.active>a{border-bottom-color:#fff}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:5px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs-justified>.active>a{border-bottom-color:#fff}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca}.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;z-index:1000;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-collapse .navbar-nav.navbar-left:first-child{margin-left:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:-15px}.navbar-collapse .navbar-text:last-child{margin-right:0}}.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-header,.container>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;border-width:0 0 1px}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;z-index:1030}.navbar-fixed-bottom{bottom:0;margin-bottom:0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-text{float:left;margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{margin-right:15px;margin-left:15px}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#ccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e6e6e6}.navbar-default .navbar-nav>.dropdown>a:hover .caret,.navbar-default .navbar-nav>.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.open>a .caret,.navbar-default .navbar-nav>.open>a:hover .caret,.navbar-default .navbar-nav>.open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.navbar-default .navbar-nav>.dropdown>a .caret{border-top-color:#777;border-bottom-color:#777}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#eee}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.btn .badge{position:relative;top:-1px}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.thumbnail{display:inline-block;display:block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img{display:block;height:auto;max-width:100%}a.thumbnail:hover,a.thumbnail:focus{border-color:#428bca}.thumbnail>img{margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.alert-warning hr{border-top-color:#f8e5be}.alert-warning .alert-link{color:#a47e3c}.alert-danger{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table{margin-bottom:0}.panel>.panel-body+.table{border-top:1px solid #ddd}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;margin-bottom:0;font-size:16px}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group .panel{margin-bottom:0;overflow:hidden;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-warning{border-color:#fbeed5}.panel-warning>.panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#fbeed5}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#fbeed5}.panel-danger{border-color:#eed3d7}.panel-danger>.panel-heading{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#eed3d7}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#eed3d7}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}body.modal-open,.modal-open .navbar-fixed-top,.modal-open .navbar-fixed-bottom{margin-right:15px}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{z-index:1050;width:auto;padding:10px;margin-right:auto;margin-left:auto}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{right:auto;left:50%;width:600px;padding-top:30px;padding-bottom:30px}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.5)),to(rgba(0,0,0,0.0001)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.0001)),to(rgba(0,0,0,0.5)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;left:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.affix{position:fixed}@-ms-viewport{width:device-width}@media screen and (max-width:400px){@-ms-viewport{width:320px}}.hidden{display:none!important;visibility:hidden!important}.visible-xs{display:none!important}tr.visible-xs{display:none!important}th.visible-xs,td.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}tr.visible-xs.visible-sm{display:table-row!important}th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}tr.visible-xs.visible-md{display:table-row!important}th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}tr.visible-xs.visible-lg{display:table-row!important}th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell!important}}.visible-sm{display:none!important}tr.visible-sm{display:none!important}th.visible-sm,td.visible-sm{display:none!important}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}tr.visible-sm.visible-xs{display:table-row!important}th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}tr.visible-sm.visible-md{display:table-row!important}th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}tr.visible-sm.visible-lg{display:table-row!important}th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell!important}}.visible-md{display:none!important}tr.visible-md{display:none!important}th.visible-md,td.visible-md{display:none!important}@media(max-width:767px){.visible-md.visible-xs{display:block!important}tr.visible-md.visible-xs{display:table-row!important}th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}tr.visible-md.visible-sm{display:table-row!important}th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}tr.visible-md.visible-lg{display:table-row!important}th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell!important}}.visible-lg{display:none!important}tr.visible-lg{display:none!important}th.visible-lg,td.visible-lg{display:none!important}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}tr.visible-lg.visible-xs{display:table-row!important}th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}tr.visible-lg.visible-sm{display:table-row!important}th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}tr.visible-lg.visible-md{display:table-row!important}th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}tr.hidden-xs{display:table-row!important}th.hidden-xs,td.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs{display:none!important}tr.hidden-xs{display:none!important}th.hidden-xs,td.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm{display:none!important}tr.hidden-xs.hidden-sm{display:none!important}th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md{display:none!important}tr.hidden-xs.hidden-md{display:none!important}th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg{display:none!important}tr.hidden-xs.hidden-lg{display:none!important}th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs{display:none!important}tr.hidden-sm.hidden-xs{display:none!important}th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}tr.hidden-sm{display:none!important}th.hidden-sm,td.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md{display:none!important}tr.hidden-sm.hidden-md{display:none!important}th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg{display:none!important}tr.hidden-sm.hidden-lg{display:none!important}th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs{display:none!important}tr.hidden-md.hidden-xs{display:none!important}th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm{display:none!important}tr.hidden-md.hidden-sm{display:none!important}th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}tr.hidden-md{display:none!important}th.hidden-md,td.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg{display:none!important}tr.hidden-md.hidden-lg{display:none!important}th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs{display:none!important}tr.hidden-lg.hidden-xs{display:none!important}th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm{display:none!important}tr.hidden-lg.hidden-sm{display:none!important}th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md{display:none!important}tr.hidden-lg.hidden-md{display:none!important}th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg{display:none!important}tr.hidden-lg{display:none!important}th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print{display:none!important}tr.visible-print{display:none!important}th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print{display:none!important}tr.hidden-print{display:none!important}th.hidden-print,td.hidden-print{display:none!important}}sidekiq-4.0.1/web/assets/images/0000755000175600017570000000000012631157272015540 5ustar pravipravisidekiq-4.0.1/web/assets/images/status-sd8051fd480.png0000755000175600017570000000433712631157272021273 0ustar pravipraviPNG  IHDRn0cIDATxyXSW_6RvM6F*#*NcשkQHA83* @"YD IXB^=91 r=r=ޗb C LmS/iwh~&ӈ=>A$_` =XoYQ#32eO*o+= ,D;& a!qkקzaShDNv_Q2<_F=+lp@J?O0'ۜ?{qB;uUвaQԸTn8y5OҙhH,3GdxV+Z&ZY]F[p!V_}2ǵ'nА;}]qqGJXȬDM)uŅJ}&~H ~HS 3gдQ)ojPqG3>]f e%_ 1NJ_Xu7~pwe2% 2]<7. )^R0T.J=!GjqKmF P %*)Uŏ3Y 8-J-OVW& č('xYp8ϻh+R+QWۿvp'b`2 u5HhP\.e~a)߱&s!Qy' l# $hts乄xޯԖ/*@~xӹ*4o[Ug(`00ijHa\=>j^|D[76jEWl׮xTn`  3W4DB ܴ_;&EF˸=2Xu>3ciN#`{nRx},tB38c2jx2p>K2ީM!ـK0@0U~G=ՅS =j5 524i e P]1G"*]Jj]Ya]gk=V54d[Mu}'@VIK|zkt76sיx{myyJuU <>zmeUsF,H`Fp!ErH}5 10c` 10c` 10LUΖ7br3VL_m,/.ȷӌ)av1"rq{⳸NRb%e&p.-luJt D:EB+*jA)N58h&$JTx<0js,#ˏ%opjX["Ig6{XYq2W3d~[H I Bzĵ%'Qq`myQPd (63"+8Ԡ 1QMzR#eKhWh jPMDqin'U-cTjDhR(v"JW"1աntA蕵XIۍ̗(@nmloNyLPө&{Dȋjd/C lMM*ƺtf54(hEE+T_[=>;3:TG!A(ԻosÄ'x^1k|V _zSù]T-Gv.d͝owƊrxzM-.ȱO/XVICm[c +4q~H:5l~9>xYI=gnǤ8&XT͝X8:#+vǵq;1=^N%p&F#IENDB`sidekiq-4.0.1/web/assets/images/status/0000755000175600017570000000000012631157272017063 5ustar pravipravisidekiq-4.0.1/web/assets/images/status/idle.png0000755000175600017570000000367412631157272020523 0ustar pravipraviPNG  IHDR;0tEXtSoftwareAdobe ImageReadyqe<hiTXtXML:com.adobe.xmp [IDATxb?8f˺DΆk!U/ cՠ+vlV9YNMa8I8hvocy7x\Y _&W ~x/Ͽ~h /GX#+o%,ļ%4&RJ4HI4xS'9Ź 3 = $$vkmY5N|/rlNG^<(/Tjl^kxуm޾ɺd5|l쿀Q>~}˭ׯ%y> U- ÿx興M#%ֈ?,x,d/ ꥪE6rY rywrH-Z/W> j%02A-&%ןX  Q d%W7gpP1# aѻ4ϟ1_`Y_0/Nه|0/FMUPOrP21DNfr',u&b{zʄi$[v \A/\'E_RbrYϦ g33'bnVL`>Akmd#|+;Ixz\S >f+$&oђzw qގ$e'%Q 0Gt祧o -]/̛LT*4)HeEQ43⓯̗>Q܃N_g>15هwq2Bed>Ntx `9wT(IENDB`sidekiq-4.0.1/web/assets/images/status/active.png0000755000175600017570000000404112631157272021046 0ustar pravipraviPNG  IHDR;0tEXtSoftwareAdobe ImageReadyqe<hiTXtXML:com.adobe.xmp !\OIDATxb`@ q1##6Ƃ-ېųf 0Ϗ_p]\8(:rZi\XPfdg4۫v7즑au+ n,Ȯu1p1_+9u*5rIz \vwB}طBDr*>`AFs̋?oo>XC3/ 1&vpX?SWn?}{ԼFr.f[gמZ LJo>{*zBS9ȱg@%L>}p)(Yx.Cr<9t.ǻ0qf6&%#]f ku4 ~T}$ͫv6۾xk[%Ź%?+zلϭ߅ncw+"e7_Qﹾ<@f0#<5(aA+pwbK6g'/O|~R닷j8EsZ_clg3l3./ $gJFFISFӫܸWS-(hm))A B$Z*HcjcR `zkv`Onf`1|+ aX@I捲ǔw~wI_:5^] *䟿E*"OL^cRye5?~ he녗Wla(-DWF dqiTA DNboYyȘ[RJ$'#3V=KA$/<<3b SAi LK 012PXB ;6}=| } S.YLlL- ZL\zvZ`Y|E >VZ6) /3L- ' 5#+/ZV`V V+~Ȭ(1q6ZNA/I ?*ZSSHJŅ-=?y7}}B_ܼ_-"MJWn]7k;Xa)< b LIENDB`sidekiq-4.0.1/web/assets/images/logo.png0000755000175600017570000001005712631157272017214 0ustar pravipraviPNG  IHDRNUEtEXtSoftwareAdobe ImageReadyqe<ViTXtXML:com.adobe.xmp K oIDATx]ye٘AMI?B<qCZ%(PA˛R9PlRd<@$pS1&"ݟyuoL3z5====~{uidXLADOp-¦)}.91h Щ@1h5zͪ%q3ZGo{ ؕXUt1{8Ol9 .qbw= gȉn&'.U޴נZE0ۼ-(/6qbʀl7 8oxfU66ME=k>yCQD8I%UI#Q z`q@E3æby<$h>IP$Xy$:<%l2Pu8@C㿣2mA|qD^~D{؏El/gPǍw4߲ޯzBg~8|f$t 6/*er@N645Te0M;ߢA؅`'mR{-I`"٭´@" $~/+sn{/U~x!//ؿ~>`sV].'sѰgu*Ux\:^Um <.ԯ=`w[,>=C"e K +jfS]Bl5]l>H_ưytȯ5|"E-٨-S,/%5NYQThcbk`g *(PCqTdB,GVӈ1e _eyHxH4J"2EߡܷdTY=EM\wd qKf=(>Eu,#c& &u-Tٻ/2RIZGʓg-(`'М! Ncv"` xͫJzGC%&I[fQ'|2Y`)`wvOgbDlm'tH|F~u6sN[;Ը]#)AxK**!{ k]AP۱<5zHXeSJ>W`hJ㍮*UzrH D n7oƥVS$p"* #$[5nBzNz\"1SYCtCo*5юDxV!$zAfZR*9)ﳥB[I@g7GWݽ= :aͻ@*7VOy_zԪBؗ&Ǻi38I;wc r}Zi eySLY}:7sZ帹Y{4qz.'7Pey/ ā\}>o;7LPeگr䭮q+rQS`[.tGU2QQj~%𢟐+hgGͨPjjqFqN"t~_M3FmqN?pꠜg{>N>q[ NZ%( /QA?4Lw{ \]88pkP bGXSN>UM}WtM&+㩘I9ת}j8hvǑ+ 4f(Emf=,O{nk ߰*MPM]),8oIFi`i/j|&IENDB`sidekiq-4.0.1/web/assets/images/favicon.ico0000644000175600017570000001246612631157272017672 0ustar pravipravi h& (  @2222m2222222222!222222222222222222222222'22A22222222K22?22222222C222222_22!222222O2222222222222222222222222222A222222222222222222W22222222222222]2222m22222222I22i222222i222222 2222222222%2212222C222272222222222e22_222222222222[2222222222{22 22( @ 2222#2222222222222222222222222222U22222212222w222222!22u222222522u22222222s22222222s22222222s2222222222s22222222K2222222222y22222222Q222222 22S22222222222222a22222222222222e2222222222O2222222222222222222222222222222222u222222222222222222222222?2222222222222222222222222222222222222222222222222222222222222222222222K22222222222222222222222222222222K22222222222222222222222222222222222222 222222K222222222222222222222222222222222222i22 22222222222222222222'22E2222222222222222222222222222222222222222[2222222222222222+222222222222222222222222-2222222222222222 2222222222222222222222W222222222222222222222222S222222-222222 222222{22222272222S2222G222222222222C2222 22 222222O2222 22 222222+22 222222Y22;2222222222c2222q2222222222%222222922/2222sidekiq-4.0.1/web/assets/images/bootstrap/0000755000175600017570000000000012631157272017555 5ustar pravipravisidekiq-4.0.1/web/assets/images/bootstrap/glyphicons-halflings.png0000755000175600017570000003077712631157272024430 0ustar pravipraviPNG  IHDRtEXtSoftwareAdobe ImageReadyqe<1IDATx}ml\EW^ɺD$|nw';vю8m0kQSnSV;1KGsԩ>UoTU1cƖYuּca&#C,pؚ>kں ULW -sn3Vq~NocI~L{- H8%_M£wB6EW,ĢpY2+(Y@&A/3kXhߍ-aA<>P'\J;(}#Qz:4%m?nfntK*l9J+DIYu1YZ^(]YYEf@ОlXz]Ut u &5-PW}@t|#LY=s܂,w#+R+?Ƌax X0"ea)tG*ԡwVwV^rf%xB(qּ4>WG#lWU<ЁXJVѶlR$kDVrI7:X%X1NEzw;y9z9O%~~uɗ*=Ixcy}Y(ou ±N$^j e\iX񝜬];Y-rѲ&>!zlYaVHVN԰9=]=mRMdOUC JUiT}rWW'ڹu)ʢF"YU#P׾&ܑЅROwyzm$Os? +^FTIEq%&~ >M}]ԖwA? [Nteexn(措BdMTpʥnqqS?bWXmW6x*{V_!VjΧsVL^j XkQjU6sk̩n~[qǸ-` O:G7l"ksRe2vQ=QƼJUX`gQy~ ďKȰE]#P:td\T/u;س:Jc-%'e q ?j"/yh48Zi1|JUu>_N;hxwNU JQU7\j̮bT:B?6oJ1Ί%I UY-Ii4{=rǤ7@)HKJ+f4X8Cd?'j1 N< 39EWo VTGzg# %D0#ܠ3[tiآ( U,]125|Ṋfw7w u+Š]Db]K xbW ՛7|ВX㕛{UcGXk¬|(h)IUa)lp 3luPU]D)/7~4Wt5J}V X0z VM;>Gԙ^|gF:jaZ^)74C#jwr,еSlGu;1vm><)}ZQՖ&mZ:1UMB~ a:/᜗:KWWOҠ&Y2f7cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘g*3fF5LbN2#Tf=C`!ZGUe꣇e2V<1mkS4iϗ*.{N8Xaj~ڀnAx,%fE:|YDVj ¢lg6(:k~MM5?4 ]WO>诋WZiG|QGJeK[YcյpmjE\f/ǎ8&OQ3 .3tt2'-V8pXSrY#J!Q ",ub@FK:u^iy[]<.Cw+W\)b kr-.MtڀMqʄ۰#$^X$"V`T4m~w%Pp1|+&UxY8*r8:k7QЃҀT$Ўƙ S>~Sjs:5q.w&_Z.X=:ވbw` _kd{'0:ds#qi!224nq\9-KUTsSUuVo@;Uz>^=Np>oPO @I@'Gj5o*U>^*ew>ͫʧ᫠Q5 ̈́<$#5Jٻj6e)_ d]2B:^(*:8JYS鬆Kݗ ]U4_rj{5ׇaǑ/yV?GtGb@xPU7O3|鍪 IQ5QGw *(;wf0*PUU<YƔvbt5{2!,}Ҧ:)j2OkΪ' ֊0I.q\(%ojQĖՇa<ԍexAgt'[d;׸`rcdjPFU$UeJI6T&Z}z(z vfuz {}ۿߝݞlxUZ謊.Y岟b%nw@ǩS9|źs%>_o#9\EU~/ځt(r[QZuOo;!MrU]0TcpDő?.cPuF;L_Sb}R/J_+h2$ai UǩS9>Є}76rzu~国4oĨ 1J ^̘~iC޸55G׹]gwsn zTuO=?/zƲc>Οb#7ֻcgkޛTUj*-T=]uu}>ݨNЭ [ ]:%/_ Sz]6D.mD7Uƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1c>J4hPP+A;'G_XKmL5I.},wFFum$S-E-;Õ C3I-`BRx1ғTJݕ;hΊ8 DYJo;Yš5MKɰM;%Pd9KhnD[zgVh,'C p!^M(WK2X>UQ%^p8 ˽^#Ζ؄+.@gCz%ɔ-Pr KX n>=ՔѨeSvRLz5%9UQS \WիK'hp)ô Jrh M0F (f_R5///G+x 1"eS 5 :Tf=+7Qɧ\TEs༬rYs8&k#pSՊ5MTbD܊[Ng5Q\s5PB@[8ɨV1&4Wsy[Ǿ wU2V77jމd^~YfC_h;a.&M i UWpzs`>/"'OI۲y:BzdTq£=йb:"m/-/PWDQǴ͐57m`H%AV!Hԛ׿@"Qzދ|ߒT-*OU^Ҧ6!Cwk|h&Hd5LEYy'ƣ7%*{=)Z%ٝP *G]/8Lw$?8M)\į/#7Ufd7'6\h1 vIfEIr=1w\WKVZHKgZ͡$mx % `j}TuTQJZ*H>*QxkLFTyU-)ôbiA|q`F'+ 4^Qy xH)#t^?@]^`ARSqjgB:rK۷l<2-4YKhgQLxVwP~M Φ0l 3ƅaŊITȀhwJmxIMչ|U7xˆS~2ߕ?kW1kC3];YnSґAeXYz8,'x< k7Kx]$x$vgT#w;o@ z_Vmn|HֵhZg-^TAn- )@4[*9xKƋj>!,Vt:eqn8%ohS(2\Q^aigF3vTUDVlQꅧWc%Ueq4ҝº/U $_Q!>t| ,țG<tC[xTXmf|Q%d#jUՆ|; H[bά#,Ws7NT1~m&ǻ{' \㟾 bBKJo8%!$Qj:/RX)$Sy޳ 䍧RDUg_D軦J\jN֖SU;~?Ohssdƣ}6(T <_4b5 ^N N%8QejF7toMyө`)g[/|?өJuGL坕/=CTܠhdifHcǞG4,`D՞{'xG_p/5@m +$jVH3a"*ũ,,HJҵȸT^Qyo&IÉJUVwWLeM~3tA6rwɤ6տ \0HL%LX5c@HHÃZ|NV+7WM{cig*ȸU7iÉбzd * ?gtX8̝OX:]2ɍ]p^++>AVڛE{ DB.&/56ArxY#ܕy)cKQtȪ~! ;C}ʃtf{6$NVsj wupZ)zŁ|-wg+nMVj/d+U~ͯi:_ix whqr>駃-x뼬)ݷyR=! ì:J/lIkV@n74758Z KJ(Uxz1w)^\ԣzȪ󲦨c2f؍v+6f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘2N oC\F1ִ UZJV̚\4Mgq1z{&YT ,HX~D u\g}x>+YdN̮ol ZX+F[/j+S~2/jV8Jr^ԉ]J}J*ۏ<2԰&JݣjOM@ѯ#0O[SXB^ uze\]dd./xXE f'vO_H${%;kt7ށmő|d{aފ^ǛڎE5ʋBr]W=_SAf(0 oU5q ,_\luz˪uz㻲o=Yi~| 0+=VJت /ލzM\zCL[U:|k*^8"\Wٚ\ .XTjX5 SkFu\1 q'mģ/QUؕ*AɽDNZ׮?_[# ˍ4:^j|5LG ||øBW{6[uQF.1$qF9IHg)\5>C#uXZ$#*<ߐsRv1Tj>Jm>*#( [Fhsש5*jQʼ&&&P犛L[Q1* ;X}Iΰ[Q?qQZ Hݙ֞VEsBCZ9JTK tup˷ /O,.kUdsOHMg4=-)+ؿh2Nw/r|WQn=GIU;'j,vfdzpe$V GTYsBZO1pj:r"nTUSCgr veAۘ˜FC+Ֆ#[JTe'v9-3 Dmӻuuz?0 o hxuY &_54=f07kלU0]D:jdw/+PGUVS<\2uatc^zYRąmC+7#,|:iNw*|^sm|X>Ъ^1\#͹ &%{,2U>ݎ.c05z# ogNO+Q쓭 ,˗-%K\[S_`y+b_94"U+Ύap}I[M,B.NtwHj漬E L߀ 0DX(kڵ NoU{gquz RwkէRx'uZ[3'zyyד%sƕ3jYF\s=m1&VAɼ?k\+]6yモ1gtOIW7al|1 >$]e 7؝WIe?ަL#>| ҭ] pM5MUdI61ԠeǼYGhOn3խR:^k_'Yuuq#p# J2xl>OjcY馃!ڡ+sZ/ D}2AY mpc#<'xSKx`*W[,e|6BH)㶤kjpDU(2qzx9*tqa/, Z[ 0>Ө֜xN)fă@qըFU՝w(a;ˋ>|Tc|w2eiT]*!_\WG{ ]^݅Z5t|6oYHaO@= my^akE.uz]#٥hWv(:,6A߉JFa\ wWex>vetuMYA>).,;ɦCbwjE)W Fӫ@s4e6^Q9oI}4x<.B?B߫#$Hx.x9,a!RTpgd5xBe.L7@* AsduttSVUaRU|I xG߃$T񭟬#_IFMŒ_X@foQIDII?|%$r {ENĸwޕqq?Dؽ}}o/`ӣCTi /ywO rD 9YUD] Ή@s]+'UaL} hrU'7:sU|k)H@hNq#ϵ8y˭Xű#w 1!흉R'7fuד0p!WÖW+Nmp\-ioD$g٠˅%%ÐmV]̱rw*Z}y+L Nouj}xt)lStuqxmNyKUOnDbhf}k>6ufT%{ <񐮸mjFcmUïc;w8@dGFUA& =nq5]iP}z:k⼶-ʓ Κl*'UzaxWFdZzTNRs+# wzgi:MBqtM l#^'Gߣ*^t{=rERnQ$adJl02%Tڊ^<~g?Of*U^?:N+o[PUs|QR']V-L)H K䐞 mYn\4}YVD hR;g-'3aסM Dh}1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌk*Ț4`L$b U4\dt'>HȄ|.+Y+/Gy2OCWv3v,'kia W O6߯E=Hv $LlxI躍/}^]x\3 ɮ5 QT&G9Ay^i}O[5ޱwq4,s JJI.myE^%'VB~dׯ}*j* ~uTk\fKЬ*Y]_v'I˨鑩6Xo'j&uɧngT]oڌ9\*wVHӖ| >:5EF'J ɝ`!A e~_;5ױϊ镋m_&OVi<}"靍hW9X6KPƣ G"ƭ?/O^hCHLciPj)}QQզ#tMg9 xGw~d;_J+RỲ<;e 5/Qs/5N[!a+NPb+ѺI}-t_qU=MKʞY5no*vvbʊ{]| ~ Z{-끇^FVviϵ3Ya=6ndS;-ʹ^;uꪪ^ |=_w+"i&4l#wir|W3U$"J~O@]~tRJVMHw:̦@?>O?vdrtS*$&~1>Z}^nL(]f*&*QaIꝄ|3*O?r?*4Gyz[k/tkQϖWCCKk/x5|S*`ϹγQEwy o KYqTb$-/PtsZNKQ*>ݢU@Џ"JQ;¹& Lx;+T /+O赟> (T?ķD^N*'p$IW֐W~ =J|_UTe7ְP`;CYjk=sU[mߙ-;};2|wo1p0~>0m @Jrǟcٷ4͜?q\UUIV?2L/+Шꄾ< ܇^T ?tj\JrҀB*=km X,n}aՒIadp׷ll{\6v8RꅟҲf1F|Տ;e=\D ,D:ψrxQT◎*|{nS 9~=}ӕG~%j:Dj<ឫ:jO% $T8!jvm|'OЗ¹➱z\vsIv`Ȕʨj-^$-^G Q{m`T#c֞㸝|n.ߪN$O JUVʼt,jg-mסּNV z:(Ι*|1Ux=Yk*t MNNDUhK ؞X(刄Rv!#B_cxRŹoE5Dg>?fXQQ˔|@"աMveC>mO$H#]Y I=)_`k* :a>!X!W^wҒl'<;vwgIt_?Jh`#E:fdx=6Wu<Ӌd2di˂c#h¬c4?<HFYoVpN;ݷJ\ >` (t3{>⦊;;qFx4YcS$w.da*k|Q,+xs^K߫P^nO֮L5mIwl?-.ʲJ8 F B.-:2Ȕ!/A#b_m%I($|PZ[1G{^#o>3mw?'cx[^:Wk/`'=~֥W(gQbfv7UzM3+؍K:4|GCtA+Kʨ{@Ɩ [05E|yn4MIENDB`sidekiq-4.0.1/web/assets/images/bootstrap/glyphicons-halflings-white.png0000755000175600017570000002111112631157272025524 0ustar pravipraviPNG  IHDRӳ{PLTEmmmⰰᒒttt󻻻bbbeeeggg𶶶xxx󛛛Ƽ몪֢UUU鿿rOtRNS#_ /oS?C kDOS_6>4!~a @1_'onҋM3BQjp&%!l"Xqr; A[<`am}43/0IPCM!6(*gK&YQGDP,`{VP-x)h7e1]W$1bzSܕcO]U;Zi'y"؆K 64Y*.v@c.};tN%DI !ZЏ5LH26 ɯ" -bE,,)ʏ B>mn6pmRO wm@V#?'CȑZ#qb|$:)/E%nRqChn%i̓}lm ?idd",`H"r.z~(bQU&)5X#EMR<*p[[%.Ọk7lIoJF lV!̡ăuH`&,zRk$|$lXbjߪdU?Σ$HW$U'HE3*խU\}( zhVk}guRk$%|T|ck獳"D_W+.Q)@ƽHbslTDR2Xm#a 3lYzj㒚#! 4J8(cvt]aT D ΅Q?^-_^$:\V $N|=(vZ'q6Z׆B5V!y3K㱿bv4xR]al!IoP@tVyL٪mlڿIUb|[*lke'*WddDӝ}\W_WߝrN?vޫ۲X%0uoui*JVƦb%}i5IYlNE-wςf_W3mI-mQ)S kTC7m<"܌bT|'$ҘR&>O p6tSN\ׯLm\r@3uT b7t.5.q3r0=8TiJ\6uF R32^'ŪxI F8O{%8kJMSȴdBEdWCYO:/ON/I_=xFE! =i:o~ y?''[͓[͓[͓[͓[ͭ.U>$PƦc%]\c:| ,eSZ,oXrX!R@Zv 0>?* <|N60;{ad2v+D^t[q!۞V}fۨϏYeॗ)Vyl|" fUq@Ǽ4Y-Y-!6aB:o%JIUQ|UKO`=\ :0x Pau@!KPdxhw1>$j΍vZdxSUA&[URd7øzk/rU^w:I.VǮc>q.!zSr&2)Wg R -iQ 8Pa\ОU%iݡU_=p Lu(N?0?Æ:]άtB%U|NsorNf ,P !v" Y6hL_@@bscqgv4||0lϟ$S9bʱj#~?o}}7sAPm:IV=n !{{hEࢪ8suoLT$;VscqD3 ༂3.DBB4&V' T `D6Ϸqyj8V*X%@s\jrN$|=5Ά 'mUiKi%CI:ssaƅ`*`=l)>u՘MeuSI_OL_}o&jzp{lu:O)s%Q@$<]f xO%PCbhr2PKpf5Në3^o]eJiB464^tuٲU֌:G4'22YpuG'/Py4?.SBP_>I 1t3ΓBɭɭɭɭVVVVVs]!67(g y@ 4>Q VF}^Xׇڼje26 L%YGh lC})< !EEPZWZV+@†R 5{@ouɐ4&H6ey V݀VťcqZޒrJyByFzFN$Hb*+jՏqэ ګkݿUXle1d0d^-B%} {Y%r*j5Ak5u",:~ҸY~ hSA~6 fulՇf{ȵQtATHZkƭ/_Sn u']b]|m`BāJ,O$du]Zs FL:aǙT4o~by?wpj滥A(x]†f~an֧/^dڲcՇ,!1i&xi_VK@ip̓9Vi%a; L?0J*Ū5U'x^6V[^ {eU|:0=0d۫o*Jq%[YN.sQLud[29I:WnmXlڃ6!lNlVէKUjV\J%UߊBLcKfb>a=b~R]aG%[js@/9MطݘU>yɲX@} Ftg^vO\Ӹwvpz3K5i!$P>ā'VƛL2r@UMKZ6tw맟¦bm1h||]}~0MjA(JJP68C&yr׉e}j_cJ?I0k>šW |Bޝ."TEXd 8!cw*E(J)![W"j_ТeX_XB;oO0~?:PC (.[!Wq%*leY)E<^KZT60.#A\5;Rmtkd/8)5~^0 #Ckgey)ͶԺ6ĥ<(?&uAVm0^h.txR*a':,H|ō l5z;8+e#b'#|}2w(|KcJ l6 w^Տoi3H R ̔9,YgPְ:N [5SR![)]i}`mN4Хv`|;f(FltL8÷Z#AO%Y)NU5YedJE3dZذݣHT1 ;8MjnʏӤqp 1h^<<>yt{?|'j)}YUU{@V/J1F+7䀉[OWO[ yUY!?BD%DWj>-Ai6xz)U R7 d@g\so)a4zf[W+> P> |qLG8vȣlj2Zt+VA6gT *ʆUz(m)CD `He/.:zN9pgo &NC׃އ>Wհ_Hj)Xe6F7pm-`'c.AZ=^e8F;{Rtn(z!S7o Iew3]bܗ85|iϠRJkʱZRO+8U&:]ZieR(JMޗ7Z@5a^\GzsρU*rMezT^:ɬͦX=>$ bi>U&XQoybbGk8 Ҙn).Սo ^MmdZi$soo*{4eLbLٳ""mx:`:mk[geTެ)'0*TB{!I ''''[͓[͓[͓[͓[]Zj Q.e '/yvQ71(Z&X?(_Z){tڀmZWϏ)-C jqn,̋"IvUL!h꛿skAcrN佚фVE40yX~4zʸV㳰%,)fqtpu~  *^0:ܲ33JO(ZB?K^ v]unlWi0p6[착C_5X#[wX3b廫R{NKAe Se|wxso>P\儔ԕ6;nVmfI$V͓J-J%֌0UwYЎSnum藮xz˗VƫIvnW_qLZ"_Xz 8]Ap?C543zw({7e*Ȳ`۰!AQ:KUnz]1yVGaCm0PY ٚUx6TT&hV9V ӬzÑ 1[XzZ9erqJND/gX*9oN6D` {I%Mz9—TQ7f\"j_3~xB'ܷY]*KЌ%"5"qxq~ƕ=jS>jV&~]2xzF1X_yD<#NRB}K/iy !V^˿eJ}/FkA7 S+.(ecJ:zWZ몖wQ~ä́p6,e5,+,tv%O^OO}ן -O7>ekC6wa_C |9*WA)UJg8=:mjUvqysܒLglC6+[FSWg9wV31A ND<$5e(s[ ۨbaF.]KIENDB`sidekiq-4.0.1/web/assets/javascripts/0000755000175600017570000000000012631157272016624 5ustar pravipravisidekiq-4.0.1/web/assets/javascripts/locales/0000755000175600017570000000000012631157272020246 5ustar pravipravisidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.de.js0000644000175600017570000000063612631157272024143 0ustar pravipravi// German jQuery.timeago.settings.strings = { prefixAgo: "vor", prefixFromNow: "in", suffixAgo: "", suffixFromNow: "", seconds: "wenigen Sekunden", minute: "etwa einer Minute", minutes: "%d Minuten", hour: "etwa einer Stunde", hours: "%d Stunden", day: "etwa einem Tag", days: "%d Tagen", month: "etwa einem Monat", months: "%d Monaten", year: "etwa einem Jahr", years: "%d Jahren" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.th.js0000644000175600017570000000131012631157272024154 0ustar pravipravi// Thai jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "ที่แล้ว", suffixFromNow: "จากตอนนี้", seconds: "น้อยกว่าหนึ่งนาที", minute: "ประมาณหนึ่งนาที", minutes: "%d นาที", hour: "ประมาณหนึ่งชั่วโมง", hours: "ประมาณ %d ชั่วโมง", day: "หนึ่งวัน", days: "%d วัน", month: "ประมาณหนึ่งเดือน", months: "%d เดือน", year: "ประมาณหนึ่งปี", years: "%d ปี", wordSeparator: "", numbers: [] }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.et.js0000644000175600017570000000173012631157272024157 0ustar pravipravi// Estonian jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "tagasi", suffixFromNow: "pärast", seconds: function(n, d) { return d < 0 ? "vähem kui minuti aja" : "vähem kui minut aega" }, minute: function(n, d) { return d < 0 ? "umbes minuti aja" : "umbes minut aega" }, minutes: function(n, d) { return d < 0 ? "%d minuti" : "%d minutit" }, hour: function(n, d) { return d < 0 ? "umbes tunni aja" : "umbes tund aega" }, hours: function(n, d) { return d < 0 ? "%d tunni" : "%d tundi" }, day: function(n, d) { return d < 0 ? "umbes päeva" : "umbes päev" }, days: function(n, d) { return d < 0 ? "%d päeva" : "%d päeva" }, month: function(n, d) { return d < 0 ? "umbes kuu aja" : "umbes kuu aega" }, months: function(n, d) { return d < 0 ? "%d kuu" : "%d kuud" }, year: function(n, d) { return d < 0 ? "umbes aasta aja" : "umbes aasta aega" }, years: function(n, d) { return d < 0 ? "%d aasta" : "%d aastat" } }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.zh-tw.js0000644000175600017570000000073012631157272024617 0ustar pravipravi// Traditional Chinese, zh-tw jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: "從現在開始", suffixAgo: "之前", suffixFromNow: null, seconds: "不到 1 分鐘", minute: "大約 1 分鐘", minutes: "%d 分鐘", hour: "大約 1 小時", hours: "%d 小時", day: "大約 1 天", days: "%d 天", month: "大約 1 個月", months: "%d 個月", year: "大約 1 年", years: "%d 年", numbers: [], wordSeparator: "" }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.he.js0000644000175600017570000000061312631157272024142 0ustar pravipravi// Hebrew jQuery.timeago.settings.strings = { prefixAgo: "לפני", prefixFromNow: "מעכשיו", suffixAgo: "", suffixFromNow: "", seconds: "פחות מדקה", minute: "דקה", minutes: "%d דקות", hour: "שעה", hours: "%d שעות", day: "יום", days: "%d ימים", month: "חודש", months: "%d חודשים", year: "שנה", years: "%d שנים" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.uk.js0000644000175600017570000000227312631157272024171 0ustar pravipravi// Ukrainian (function() { function numpf(n, f, s, t) { // f - 1, 21, 31, ... // s - 2-4, 22-24, 32-34 ... // t - 5-20, 25-30, ... var n10 = n % 10; if ( (n10 == 1) && ( (n == 1) || (n > 20) ) ) { return f; } else if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) { return s; } else { return t; } } jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: "через", suffixAgo: "тому", suffixFromNow: null, seconds: "менше хвилини", minute: "хвилина", minutes: function(value) { return numpf(value, "%d хвилина", "%d хвилини", "%d хвилин"); }, hour: "година", hours: function(value) { return numpf(value, "%d година", "%d години", "%d годин"); }, day: "день", days: function(value) { return numpf(value, "%d день", "%d дні", "%d днів"); }, month: "місяць", months: function(value) { return numpf(value, "%d місяць", "%d місяці", "%d місяців"); }, year: "рік", years: function(value) { return numpf(value, "%d рік", "%d роки", "%d років"); } }; })();sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.id.js0000644000175600017570000000065512631157272024150 0ustar pravipravi// Indonesian jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "yang lalu", suffixFromNow: "dari sekarang", seconds: "kurang dari semenit", minute: "sekitar satu menit", minutes: "%d menit", hour: "sekitar sejam", hours: "sekitar %d jam", day: "sehari", days: "%d hari", month: "sekitar sebulan", months: "%d bulan", year: "sekitar setahun", years: "%d tahun" }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.fr.js0000644000175600017570000000070312631157272024155 0ustar pravipravi// French jQuery.timeago.settings.strings = { // environ ~= about, it's optional prefixAgo: "il y a", prefixFromNow: "d'ici", seconds: "moins d'une minute", minute: "environ une minute", minutes: "environ %d minutes", hour: "environ une heure", hours: "environ %d heures", day: "environ un jour", days: "environ %d jours", month: "environ un mois", months: "environ %d mois", year: "un an", years: "%d ans" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.el.js0000644000175600017570000000103012631157272024140 0ustar pravipravi// Greek jQuery.timeago.settings.strings = { prefixAgo: "πριν", prefixFromNow: "σε", suffixAgo: "", suffixFromNow: "", seconds: "λιγότερο από ένα λεπτό", minute: "περίπου ένα λεπτό", minutes: "%d λεπτά", hour: "περίπου μία ώρα", hours: "περίπου %d ώρες", day: "μία μέρα", days: "%d μέρες", month: "περίπου ένα μήνα", months: "%d μήνες", year: "περίπου ένα χρόνο", years: "%d χρόνια" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.hy.js0000644000175600017570000000065512631157272024174 0ustar pravipravi// Armenian jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "առաջ", suffixFromNow: "հետո", seconds: "վայրկյաններ", minute: "մեկ րոպե", minutes: "%d րոպե", hour: "մեկ ժամ", hours: "%d ժամ", day: "մեկ օր", days: "%d օր", month: "մեկ ամիս", months: "%d ամիս", year: "մեկ տարի", years: "%d տարի" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.sv.js0000644000175600017570000000065112631157272024200 0ustar pravipravi// Swedish jQuery.timeago.settings.strings = { prefixAgo: "för", prefixFromNow: "om", suffixAgo: "sedan", suffixFromNow: "", seconds: "mindre än en minut", minute: "ungefär en minut", minutes: "%d minuter", hour: "ungefär en timme", hours: "ungefär %d timmar", day: "en dag", days: "%d dagar", month: "ungefär en månad", months: "%d månader", year: "ungefär ett år", years: "%d år" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.ca.js0000644000175600017570000000062512631157272024134 0ustar pravipravi// Catalan jQuery.timeago.settings.strings = { prefixAgo: "fa", prefixFromNow: "d'aqui a", suffixAgo: null, suffixFromNow: null, seconds: "menys d'1 minut", minute: "1 minut", minutes: "uns %d minuts", hour: "1 hora", hours: "unes %d hores", day: "1 dia", days: "%d dies", month: "aproximadament un mes", months: "%d mesos", year: "aproximadament un any", years: "%d anys" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.nl.js0000644000175600017570000000072312631157272024161 0ustar pravipravi// Dutch jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: "", suffixAgo: "geleden", suffixFromNow: "van nu", seconds: "minder dan een minuut", minute: "ongeveer een minuut", minutes: "%d minuten", hour: "ongeveer een uur", hours: "ongeveer %d uur", day: "een dag", days: "%d dagen", month: "ongeveer een maand", months: "%d maanden", year: "ongeveer een jaar", years: "%d jaar", wordSeparator: " ", numbers: [] }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.cs.js0000644000175600017570000000057412631157272024161 0ustar pravipravi// Czech jQuery.timeago.settings.strings = { prefixAgo: "před", prefixFromNow: null, suffixAgo: null, suffixFromNow: null, seconds: "méně než minutou", minute: "minutou", minutes: "%d minutami", hour: "hodinou", hours: "%d hodinami", day: "1 dnem", days: "%d dny", month: "1 měsícem", months: "%d měsíci", year: "1 rokem", years: "%d roky" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.rs.js0000644000175600017570000000256212631157272024177 0ustar pravipravi// Serbian (function () { var numpf; numpf = function (n, f, s, t) { var n10; n10 = n % 10; if (n10 === 1 && (n === 1 || n > 20)) { return f; } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { return s; } else { return t; } }; jQuery.timeago.settings.strings = { prefixAgo: "pre", prefixFromNow: "za", suffixAgo: null, suffixFromNow: null, second: "sekund", seconds: function (value) { return numpf(value, "%d sekund", "%d sekunde", "%d sekundi"); }, minute: "oko minut", minutes: function (value) { return numpf(value, "%d minut", "%d minuta", "%d minuta"); }, hour: "oko jedan sat", hours: function (value) { return numpf(value, "%d sat", "%d sata", "%d sati"); }, day: "jedan dan", days: function (value) { return numpf(value, "%d dan", "%d dana", "%d dana"); }, month: "mesec dana", months: function (value) { return numpf(value, "%d mesec", "%d meseca", "%d meseci"); }, year: "pre godinu dana", years: function (value) { return numpf(value, "%d godinu", "%d godine", "%d godina"); }, wordSeparator: " " }; }).call(this); sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.lt.js0000644000175600017570000000061512631157272024167 0ustar pravipravi//Lithuanian jQuery.timeago.settings.strings = { prefixAgo: "prieš", prefixFromNow: null, suffixAgo: null, suffixFromNow: "nuo dabar", seconds: "%d sek.", minute: "min.", minutes: "%d min.", hour: "val.", hours: "%d val.", day: "1 d.", days: "%d d.", month: "mėn.", months: "%d mėn.", year: "metus", years: "%d metus", wordSeparator: " ", numbers: [] }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.en.js0000644000175600017570000000070712631157272024154 0ustar pravipravi// English (Template) jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "ago", suffixFromNow: "from now", seconds: "less than a minute", minute: "about a minute", minutes: "%d minutes", hour: "about an hour", hours: "about %d hours", day: "a day", days: "%d days", month: "about a month", months: "%d months", year: "about a year", years: "%d years", wordSeparator: " ", numbers: [] }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.it.js0000644000175600017570000000055112631157272024163 0ustar pravipravi// Italian jQuery.timeago.settings.strings = { suffixAgo: "fa", suffixFromNow: "da ora", seconds: "meno di un minuto", minute: "circa un minuto", minutes: "%d minuti", hour: "circa un'ora", hours: "circa %d ore", day: "un giorno", days: "%d giorni", month: "circa un mese", months: "%d mesi", year: "circa un anno", years: "%d anni" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.sk.js0000644000175600017570000000060312631157272024162 0ustar pravipravi// Slovak jQuery.timeago.settings.strings = { prefixAgo: "pred", prefixFromNow: null, suffixAgo: null, suffixFromNow: null, seconds: "menej než minútou", minute: "minútou", minutes: "%d minútami", hour: "hodinou", hours: "%d hodinami", day: "1 dňom", days: "%d dňami", month: "1 mesiacom", months: "%d mesiacmi", year: "1 rokom", years: "%d rokmi" }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.hr.js0000644000175600017570000000257612631157272024171 0ustar pravipravi// Croatian (function () { var numpf; numpf = function (n, f, s, t) { var n10; n10 = n % 10; if (n10 === 1 && (n === 1 || n > 20)) { return f; } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { return s; } else { return t; } }; jQuery.timeago.settings.strings = { prefixAgo: "prije", prefixFromNow: "za", suffixAgo: null, suffixFromNow: null, second: "sekundu", seconds: function (value) { return numpf(value, "%d sekundu", "%d sekunde", "%d sekundi"); }, minute: "oko minutu", minutes: function (value) { return numpf(value, "%d minutu", "%d minute", "%d minuta"); }, hour: "oko jedan sat", hours: function (value) { return numpf(value, "%d sat", "%d sata", "%d sati"); }, day: "jedan dan", days: function (value) { return numpf(value, "%d dan", "%d dana", "%d dana"); }, month: "mjesec dana", months: function (value) { return numpf(value, "%d mjesec", "%d mjeseca", "%d mjeseci"); }, year: "prije godinu dana", years: function (value) { return numpf(value, "%d godinu", "%d godine", "%d godina"); }, wordSeparator: " " }; }).call(this);sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.ko.js0000644000175600017570000000050412631157272024156 0ustar pravipravi// Korean jQuery.timeago.settings.strings = { suffixAgo: "전", suffixFromNow: "후", seconds: "1분 이내", minute: "1분", minutes: "%d분", hour: "1시간", hours: "%d시간", day: "하루", days: "%d일", month: "한 달", months: "%d달", year: "1년", years: "%d년", wordSeparator: " " };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.da.js0000644000175600017570000000061312631157272024132 0ustar pravipravi// Danish jQuery.timeago.settings.strings = { prefixAgo: "for", prefixFromNow: "om", suffixAgo: "siden", suffixFromNow: "", seconds: "mindre end et minut", minute: "ca. et minut", minutes: "%d minutter", hour: "ca. en time", hours: "ca. %d timer", day: "en dag", days: "%d dage", month: "ca. en måned", months: "%d måneder", year: "ca. et år", years: "%d år" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.ro.js0000644000175600017570000000055212631157272024170 0ustar pravipravi// Romanian $.timeago.settings.strings = { prefixAgo: "acum", prefixFromNow: "in timp de", suffixAgo: "", suffixFromNow: "", seconds: "mai putin de un minut", minute: "un minut", minutes: "%d minute", hour: "o ora", hours: "%d ore", day: "o zi", days: "%d zile", month: "o luna", months: "%d luni", year: "un an", years: "%d ani" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.en-short.js0000644000175600017570000000053712631157272025312 0ustar pravipravi// English shortened jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "", suffixFromNow: "", seconds: "1m", minute: "1m", minutes: "%dm", hour: "1h", hours: "%dh", day: "1d", days: "%dd", month: "1mo", months: "%dmo", year: "1yr", years: "%dyr", wordSeparator: " ", numbers: [] }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.fa.js0000644000175600017570000000115512631157272024136 0ustar pravipravi // Persian // Use DIR attribute for RTL text in Persian Language for ABBR tag . // By MB.seifollahi@gmail.com jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "پیش", suffixFromNow: "از حال", seconds: "کمتر از یک دقیقه", minute: "حدود یک دقیقه", minutes: "%d دقیقه", hour: "حدود یک ساعت", hours: "حدود %d ساعت", day: "یک روز", days: "%d روز", month: "حدود یک ماه", months: "%d ماه", year: "حدود یک سال", years: "%d سال", wordSeparator: " " };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.pt.js0000644000175600017570000000063012631157272024170 0ustar pravipravi// Portuguese jQuery.timeago.settings.strings = { suffixAgo: "atrás", suffixFromNow: "a partir de agora", seconds: "menos de um minuto", minute: "cerca de um minuto", minutes: "%d minutos", hour: "cerca de uma hora", hours: "cerca de %d horas", day: "um dia", days: "%d dias", month: "cerca de um mês", months: "%d meses", year: "cerca de um ano", years: "%d anos" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.no.js0000644000175600017570000000062112631157272024161 0ustar pravipravi// Norwegian jQuery.timeago.settings.strings = { prefixAgo: "for", prefixFromNow: "om", suffixAgo: "siden", suffixFromNow: "", seconds: "mindre enn et minutt", minute: "ca. et minutt", minutes: "%d minutter", hour: "ca. en time", hours: "ca. %d timer", day: "en dag", days: "%d dager", month: "ca. en måned", months: "%d måneder", year: "ca. et år", years: "%d år" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.fi.js0000644000175600017570000000164012631157272024145 0ustar pravipravi// Finnish jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "sitten", suffixFromNow: "tulevaisuudessa", seconds: "alle minuutti", minute: "minuutti", minutes: "%d minuuttia", hour: "tunti", hours: "%d tuntia", day: "päivä", days: "%d päivää", month: "kuukausi", months: "%d kuukautta", year: "vuosi", years: "%d vuotta" }; // The above is not a great localization because one would usually // write "2 days ago" in Finnish as "2 päivää sitten", however // one would write "2 days into the future" as "2:n päivän päästä" // which cannot be achieved with localization support this simple. // This is because Finnish has word suffixes (attached directly // to the end of the word). The word "day" is "päivä" in Finnish. // As workaround, the above localizations will say // "2 päivää tulevaisuudessa" which is understandable but // not as fluent.sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.zh-cn.js0000644000175600017570000000071412631157272024567 0ustar pravipravi// Simplified Chinese jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: "从现在开始", suffixAgo: "之前", suffixFromNow: null, seconds: "不到 1 分钟", minute: "大约 1 分钟", minutes: "%d 分钟", hour: "大约 1 小时", hours: "大约 %d 小时", day: "1 天", days: "%d 天", month: "大约 1 个月", months: "%d 月", year: "大约 1 年", years: "%d 年", numbers: [], wordSeparator: "" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.bg.js0000644000175600017570000000074112631157272024140 0ustar pravipravi// Bulgarian jQuery.timeago.settings.strings = { prefixAgo: "преди", prefixFromNow: "след", suffixAgo: null, suffixFromNow: null, seconds: "по-малко от минута", minute: "една минута", minutes: "%d минути", hour: "един час", hours: "%d часа", day: "един ден", days: "%d дни", month: "един месец", months: "%d месеца", year: "една година", years: "%d години" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.pt-br.js0000644000175600017570000000064212631157272024574 0ustar pravipravi// Brasilian Portuguese jQuery.timeago.settings.strings = { suffixAgo: "atrás", suffixFromNow: "a partir de agora", seconds: "menos de um minuto", minute: "cerca de um minuto", minutes: "%d minutos", hour: "cerca de uma hora", hours: "cerca de %d horas", day: "um dia", days: "%d dias", month: "cerca de um mês", months: "%d meses", year: "cerca de um ano", years: "%d anos" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.pl.js0000644000175600017570000000155312631157272024165 0ustar pravipravi// Polish (function() { function numpf(n, s, t) { // s - 2-4, 22-24, 32-34 ... // t - 5-21, 25-31, ... var n10 = n % 10; if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) { return s; } else { return t; } } jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: "za", suffixAgo: "temu", suffixFromNow: null, seconds: "mniej niż minutę", minute: "minutę", minutes: function(value) { return numpf(value, "%d minuty", "%d minut"); }, hour: "godzinę", hours: function(value) { return numpf(value, "%d godziny", "%d godzin"); }, day: "dzień", days: "%d dni", month: "miesiąc", months: function(value) { return numpf(value, "%d miesiące", "%d miesięcy"); }, year: "rok", years: function(value) { return numpf(value, "%d lata", "%d lat"); } }; })();sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.mk.js0000644000175600017570000000074712631157272024165 0ustar pravipravi// Macedonian (function() { jQuery.timeago.settings.strings={ prefixAgo: "пред", prefixFromNow: "за", suffixAgo: null, suffixFromNow: null, seconds: "%d секунди", minute: "%d минута", minutes: "%d минути", hour: "%d час", hours: "%d часа", day: "%d ден", days: "%d денови" , month: "%d месец", months: "%d месеци", year: "%d година", years: "%d години" } })(); sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.ar.js0000644000175600017570000000532712631157272024157 0ustar pravipravi(function() { function numpf(n, a) { return a[plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5]; } jQuery.timeago.settings.strings = { prefixAgo: "منذ", prefixFromNow: "بعد", suffixAgo: null, suffixFromNow: null, // null OR "من الآن" second: function(value) { return numpf(value, [ 'أقل من ثانية', 'ثانية واحدة', 'ثانيتين', '%d ثوانٍ', '%d ثانية', '%d ثانية']); }, seconds: function(value) { return numpf(value, [ 'أقل من ثانية', 'ثانية واحدة', 'ثانيتين', '%d ثوانٍ', '%d ثانية', '%d ثانية']); }, minute: function(value) { return numpf(value, [ 'أقل من دقيقة', 'دقيقة واحدة', 'دقيقتين', '%d دقائق', '%d دقيقة', 'دقيقة']); }, minutes: function(value) { return numpf(value, [ 'أقل من دقيقة', 'دقيقة واحدة', 'دقيقتين', '%d دقائق', '%d دقيقة', 'دقيقة']); }, hour: function(value) { return numpf(value, [ 'أقل من ساعة', 'ساعة واحدة', 'ساعتين', '%d ساعات', '%d ساعة', '%d ساعة']); }, hours: function(value) { return numpf(value, [ 'أقل من ساعة', 'ساعة واحدة', 'ساعتين', '%d ساعات', '%d ساعة', '%d ساعة']); }, day: function(value) { return numpf(value, [ 'أقل من يوم', 'يوم واحد', 'يومين', '%d أيام', '%d يومًا', '%d يوم']); }, days: function(value) { return numpf(value, [ 'أقل من يوم', 'يوم واحد', 'يومين', '%d أيام', '%d يومًا', '%d يوم']); }, month: function(value) { return numpf(value, [ 'أقل من شهر', 'شهر واحد', 'شهرين', '%d أشهر', '%d شهرًا', '%d شهر']); }, months: function(value) { return numpf(value, [ 'أقل من شهر', 'شهر واحد', 'شهرين', '%d أشهر', '%d شهرًا', '%d شهر']); }, year: function(value) { return numpf(value, [ 'أقل من عام', 'عام واحد', '%d عامين', '%d أعوام', '%d عامًا']); }, years: function(value) { return numpf(value, [ 'أقل من عام', 'عام واحد', 'عامين', '%d أعوام', '%d عامًا', '%d عام']);} }; })(); sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.hu.js0000644000175600017570000000073612631157272024170 0ustar pravipravi// Hungarian jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: null, suffixFromNow: null, seconds: "kevesebb mint egy perce", minute: "körülbelül egy perce", minutes: "%d perce", hour: "körülbelül egy órája", hours: "körülbelül %d órája", day: "körülbelül egy napja", days: "%d napja", month: "körülbelül egy hónapja", months: "%d hónapja", year: "körülbelül egy éve", years: "%d éve" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.es.js0000644000175600017570000000061612631157272024160 0ustar pravipravi// Spanish jQuery.timeago.settings.strings = { prefixAgo: "hace", prefixFromNow: "dentro de", suffixAgo: "", suffixFromNow: "", seconds: "menos de un minuto", minute: "un minuto", minutes: "unos %d minutos", hour: "una hora", hours: "%d horas", day: "un día", days: "%d días", month: "un mes", months: "%d meses", year: "un año", years: "%d años" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.uz.js0000644000175600017570000000102512631157272024202 0ustar pravipravi//Uzbek jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: "keyin", suffixAgo: "avval", suffixFromNow: null, seconds: "bir necha soniya", minute: "1 daqiqa", minutes: function(value) { return "%d daqiqa" }, hour: "1 soat", hours: function(value) { return "%d soat" }, day: "1 kun", days: function(value) { return "%d kun" }, month: "1 oy", months: function(value) { return "%d oy" }, year: "1 yil", years: function(value) { return "%d yil" }, wordSeparator: " " }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.cy.js0000644000175600017570000000064012631157272024161 0ustar pravipravi// Welsh jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: null, suffixAgo: "yn ôl", suffixFromNow: "o hyn", seconds: "llai na munud", minute: "am funud", minutes: "%d munud", hour: "tua awr", hours: "am %d awr", day: "y dydd", days: "%d diwrnod", month: "tua mis", months: "%d mis", year: "am y flwyddyn", years: "%d blynedd", wordSeparator: " ", numbers: [] }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.sl.js0000644000175600017570000000224612631157272024170 0ustar pravipravi// Slovenian with support for dual (function () { var numpf; numpf = function (n, d, m) { if (n == 2) { return d; } else { return m; } }; jQuery.timeago.settings.strings = { prefixAgo: "pred", prefixFromNow: "čez", suffixAgo: null, suffixFromNow: null, second: "sekundo", seconds: function (value) { return numpf(value, "%d sekundama", "%d sekundami"); }, minute: "minuto", minutes: function (value) { return numpf(value, "%d minutama", "%d minutami"); }, hour: "uro", hours: function (value) { return numpf(value, "%d urama", "%d urami"); }, day: "dnevom", days: function (value) { return numpf(value, "%d dnevi", "%d dnevi"); }, month: "enim mescem", months: function (value) { return numpf(value, "%d mesecema", "%d meseci"); }, year: "enim letom", years: function (value) { return numpf(value, "%d letoma", "%d leti"); }, wordSeparator: " " }; }).call(this); sidekiq-4.0.1/web/assets/javascripts/locales/README.md0000644000175600017570000000167012631157272021531 0ustar pravipravi# Locale override examples for timeago You can represent time statements in most western languages where a prefix and/or suffix is used. The default case is to use suffix only (as in English), which you do by providing the `suffixAgo` and `suffixFromNow` settings in the strings hash (earlier versions of timeago used the deprecated `ago` and `fromNow` options). If present, they are used. 2 minutes [suffixAgo] 2 minutes [suffixFromNow] In case you want to use prefix only instead of suffix (e.g. Greek), you provide the `prefixAgo` and `prefixFromNow` options in the strings hash and leave `suffixAgo` and `suffixFromNow` empty or null. [prefixAgo] 2 minutes [prefixFromNow] 2 minutes For languages where you want to use a prefix only for future tense and prefix/suffix for past tense (for example swedish), you can combine the prefix and suffixes as needed. [prefixAgo] 2 minutes [suffixAgo] [prefixFromNow] 2 minutes sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.ja.js0000644000175600017570000000063612631157272024145 0ustar pravipravi// Japanese jQuery.timeago.settings.strings = { prefixAgo: "", prefixFromNow: "今から", suffixAgo: "前", suffixFromNow: "後", seconds: "1 分未満", minute: "約 1 分", minutes: "%d 分", hour: "約 1 時間", hours: "約 %d 時間", day: "約 1 日", days: "約 %d 日", month: "約 1 月", months: "約 %d 月", year: "約 1 年", years: "約 %d 年", wordSeparator: "" }; sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.fr-short.js0000644000175600017570000000054712631157272025320 0ustar pravipravi// French shortened jQuery.timeago.settings.strings = { prefixAgo: "il y a", prefixFromNow: "d'ici", seconds: "moins d'une minute", minute: "une minute", minutes: "%d minutes", hour: "une heure", hours: "%d heures", day: "un jour", days: "%d jours", month: "un mois", months: "%d mois", year: "un an", years: "%d ans" };sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.bs.js0000644000175600017570000000227612631157272024161 0ustar pravipravi// Bosnian (function() { var numpf; numpf = function(n, f, s, t) { var n10; n10 = n % 10; if (n10 === 1 && (n === 1 || n > 20)) { return f; } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { return s; } else { return t; } }; jQuery.timeago.settings.strings = { prefixAgo: "prije", prefixFromNow: "za", suffixAgo: null, suffixFromNow: null, second: "sekund", seconds: function(value) { return numpf(value, "%d sekund", "%d sekunde", "%d sekundi"); }, minute: "oko minut", minutes: function(value) { return numpf(value, "%d minut", "%d minute", "%d minuta"); }, hour: "oko sat", hours: function(value) { return numpf(value, "%d sat", "%d sata", "%d sati"); }, day: "oko jednog dana", days: function(value) { return numpf(value, "%d dan", "%d dana", "%d dana"); }, month: "mjesec dana", months: function(value) { return numpf(value, "%d mjesec", "%d mjeseca", "%d mjeseci"); }, year: "prije godinu dana ", years: function(value) { return numpf(value, "%d godinu", "%d godine", "%d godina"); }, wordSeparator: " " }; }).call(this);sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.ru.js0000644000175600017570000000223312631157272024174 0ustar pravipravi// Russian (function() { function numpf(n, f, s, t) { // f - 1, 21, 31, ... // s - 2-4, 22-24, 32-34 ... // t - 5-20, 25-30, ... var n10 = n % 10; if ( (n10 == 1) && ( (n == 1) || (n > 20) ) ) { return f; } else if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) { return s; } else { return t; } } jQuery.timeago.settings.strings = { prefixAgo: null, prefixFromNow: "через", suffixAgo: "назад", suffixFromNow: null, seconds: "меньше минуты", minute: "минуту", minutes: function(value) { return numpf(value, "%d минута", "%d минуты", "%d минут"); }, hour: "час", hours: function(value) { return numpf(value, "%d час", "%d часа", "%d часов"); }, day: "день", days: function(value) { return numpf(value, "%d день", "%d дня", "%d дней"); }, month: "месяц", months: function(value) { return numpf(value, "%d месяц", "%d месяца", "%d месяцев"); }, year: "год", years: function(value) { return numpf(value, "%d год", "%d года", "%d лет"); } }; })();sidekiq-4.0.1/web/assets/javascripts/locales/jquery.timeago.tr.js0000644000175600017570000000051712631157272024176 0ustar pravipravi// Turkish jQuery.extend($.timeago.settings.strings, { suffixAgo: 'önce', suffixFromNow: null, seconds: '1 dakikadan', minute: '1 dakika', minutes: '%d dakika', hour: '1 saat', hours: '%d saat', day: '1 gün', days: '%d gün', month: '1 ay', months: '%d ay', year: '1 yıl', years: '%d yıl' });sidekiq-4.0.1/bin/0000755000175600017570000000000012631157272012764 5ustar pravipravisidekiq-4.0.1/bin/sidekiqload0000755000175600017570000001001212631157272015175 0ustar pravipravi#!/usr/bin/env ruby # Quiet some warnings we see when running in warning mode: # RUBYOPT=-w bundle exec sidekiq $TESTING = false #require 'ruby-prof' Bundler.require(:default) require_relative '../lib/sidekiq/cli' require_relative '../lib/sidekiq/launcher' include Sidekiq::Util # brew tap shopify/shopify # brew install toxiproxy # gem install toxiproxy require 'toxiproxy' # simulate a non-localhost network for realer-world conditions. # adding 1ms of network latency has an ENORMOUS impact on benchmarks Toxiproxy.populate([{ "name": "redis", "listen": "127.0.0.1:6380", "upstream": "127.0.0.1:6379" }]) Sidekiq.configure_server do |config| config.redis = { db: 13, port: 6380 } #config.redis = { db: 13 } config.options[:queues] << 'default' config.logger.level = Logger::ERROR config.average_scheduled_poll_interval = 2 config.reliable! if defined?(Sidekiq::Pro) end class LoadWorker include Sidekiq::Worker sidekiq_options retry: 1 sidekiq_retry_in do |x| 1 end def perform(idx) #raise idx.to_s if idx % 100 == 1 end end # brew tap shopify/shopify # brew install toxiproxy # gem install toxiproxy require 'toxiproxy' # simulate a non-localhost network for realer-world conditions. # adding 1ms of network latency has an ENORMOUS impact on benchmarks Toxiproxy.populate([{ "name": "redis", "listen": "127.0.0.1:6380", "upstream": "127.0.0.1:6379" }]) self_read, self_write = IO.pipe %w(INT TERM USR1 USR2 TTIN).each do |sig| begin trap sig do self_write.puts(sig) end rescue ArgumentError puts "Signal #{sig} not supported" end end Sidekiq.redis {|c| c.flushdb} def handle_signal(launcher, sig) Sidekiq.logger.debug "Got #{sig} signal" case sig when 'INT' # Handle Ctrl-C in JRuby like MRI # http://jira.codehaus.org/browse/JRUBY-4637 raise Interrupt when 'TERM' # Heroku sends TERM and then waits 10 seconds for process to exit. raise Interrupt when 'USR1' Sidekiq.logger.info "Received USR1, no longer accepting new work" launcher.quiet when 'USR2' if Sidekiq.options[:logfile] Sidekiq.logger.info "Received USR2, reopening log file" Sidekiq::Logging.reopen_logs end when 'TTIN' Thread.list.each do |thread| Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" if thread.backtrace Sidekiq.logger.warn thread.backtrace.join("\n") else Sidekiq.logger.warn "" end end end end def Process.rss `ps -o rss= -p #{Process.pid}`.chomp.to_i end iter = 10 count = 10_000 iter.times do arr = Array.new(count) do [] end count.times do |idx| arr[idx][0] = idx end Sidekiq::Client.push_bulk('class' => LoadWorker, 'args' => arr) end Sidekiq.logger.error "Created #{count*iter} jobs" Monitoring = Thread.new do watchdog("monitor thread") do while true sleep 2 qsize, retries = Sidekiq.redis do |conn| conn.pipelined do conn.llen "queue:default" conn.zcard "retry" end end.map(&:to_i) total = qsize + retries #GC.start Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}") if total == 0 Sidekiq.logger.error("Done") exit(0) end end end end begin #RubyProf::exclude_threads = [ Monitoring ] #RubyProf.start fire_event(:startup) Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis" Toxiproxy[:redis].downstream(:latency, latency: 1).apply do launcher = Sidekiq::Launcher.new(Sidekiq.options) launcher.run while readable_io = IO.select([self_read]) signal = readable_io.first[0].gets.strip handle_signal(launcher, signal) end end rescue SystemExit => e #Sidekiq.logger.error("Profiling...") #result = RubyProf.stop #printer = RubyProf::GraphHtmlPrinter.new(result) #printer.print(File.new("output.html", "w"), :min_percent => 1) # normal rescue => e raise e if $DEBUG STDERR.puts e.message STDERR.puts e.backtrace.join("\n") exit 1 end sidekiq-4.0.1/bin/sidekiqctl0000755000175600017570000000461412631157272015053 0ustar pravipravi#!/usr/bin/env ruby require 'fileutils' class Sidekiqctl DEFAULT_KILL_TIMEOUT = 10 attr_reader :stage, :pidfile, :kill_timeout def self.print_usage puts "#{File.basename($0)} - stop a Sidekiq process from the command line." puts puts "Usage: #{File.basename($0)} " puts " where is either 'quiet' or 'stop'" puts " is path to a pidfile" puts " is number of seconds to wait until Sidekiq exits" puts " (default: #{Sidekiqctl::DEFAULT_KILL_TIMEOUT}), after which Sidekiq will be KILL'd" puts puts "Be sure to set the kill_timeout LONGER than Sidekiq's -t timeout. If you want" puts "to wait 60 seconds for jobs to finish, use `sidekiq -t 60` and `sidekiqctl stop" puts " path_to_pidfile 61`" puts end def initialize(stage, pidfile, timeout) @stage = stage @pidfile = pidfile @kill_timeout = timeout done('No pidfile given', :error) if !pidfile done("Pidfile #{pidfile} does not exist", :warn) if !File.exist?(pidfile) done('Invalid pidfile content', :error) if pid == 0 fetch_process begin send(stage) rescue NoMethodError done "Invalid command: #{stage}", :error end end def fetch_process Process.kill(0, pid) rescue Errno::ESRCH done "Process doesn't exist", :error # We were not allowed to send a signal, but the process must have existed # when Process.kill() was called. rescue Errno::EPERM return pid end def done(msg, error = nil) puts msg exit(exit_signal(error)) end def exit_signal(error) (error == :error) ? 1 : 0 end def pid @pid ||= File.read(pidfile).to_i end def quiet `kill -USR1 #{pid}` end def stop `kill -TERM #{pid}` kill_timeout.times do begin Process.kill(0, pid) rescue Errno::ESRCH FileUtils.rm_f pidfile done 'Sidekiq shut down gracefully.' rescue Errno::EPERM done 'Not permitted to shut down Sidekiq.' end sleep 1 end `kill -9 #{pid}` FileUtils.rm_f pidfile done 'Sidekiq shut down forcefully.' end alias_method :shutdown, :stop end if ARGV.length < 2 Sidekiqctl.print_usage else stage = ARGV[0] pidfile = ARGV[1] timeout = ARGV[2].to_i timeout = Sidekiqctl::DEFAULT_KILL_TIMEOUT if timeout == 0 Sidekiqctl.new(stage, pidfile, timeout) end sidekiq-4.0.1/bin/sidekiq0000755000175600017570000000054712631157272014351 0ustar pravipravi#!/usr/bin/env ruby # Quiet some warnings we see when running in warning mode: # RUBYOPT=-w bundle exec sidekiq $TESTING = false $CELLULOID_DEBUG = false require_relative '../lib/sidekiq/cli' begin cli = Sidekiq::CLI.instance cli.parse cli.run rescue => e raise e if $DEBUG STDERR.puts e.message STDERR.puts e.backtrace.join("\n") exit 1 end sidekiq-4.0.1/lib/0000755000175600017570000000000012631157272012762 5ustar pravipravisidekiq-4.0.1/lib/generators/0000755000175600017570000000000012631157272015133 5ustar pravipravisidekiq-4.0.1/lib/generators/sidekiq/0000755000175600017570000000000012631157272016564 5ustar pravipravisidekiq-4.0.1/lib/generators/sidekiq/worker_generator.rb0000644000175600017570000000220412631157272022466 0ustar pravipravirequire 'rails/generators/named_base' module Sidekiq module Generators # :nodoc: class WorkerGenerator < ::Rails::Generators::NamedBase # :nodoc: desc 'This generator creates a Sidekiq Worker in app/workers and a corresponding test' check_class_collision suffix: 'Worker' def self.default_generator_root File.dirname(__FILE__) end def create_worker_file template 'worker.rb.erb', File.join('app/workers', class_path, "#{file_name}_worker.rb") end def create_test_file if defined?(RSpec) create_worker_spec else create_worker_test end end private def create_worker_spec template_file = File.join( 'spec/workers', class_path, "#{file_name}_worker_spec.rb" ) template 'worker_spec.rb.erb', template_file end def create_worker_test template_file = File.join( 'test/workers', class_path, "#{file_name}_worker_test.rb" ) template 'worker_test.rb.erb', template_file end end end end sidekiq-4.0.1/lib/generators/sidekiq/templates/0000755000175600017570000000000012631157272020562 5ustar pravipravisidekiq-4.0.1/lib/generators/sidekiq/templates/worker_spec.rb.erb0000644000175600017570000000027112631157272024201 0ustar pravipravirequire 'rails_helper' <% module_namespacing do -%> RSpec.describe <%= class_name %>Worker, :type => :worker do pending "add some examples to (or delete) #{__FILE__}" end <% end -%>sidekiq-4.0.1/lib/generators/sidekiq/templates/worker.rb.erb0000644000175600017570000000022212631157272023163 0ustar pravipravi<% module_namespacing do -%> class <%= class_name %>Worker include Sidekiq::Worker def perform(*args) # Do something end end <% end -%>sidekiq-4.0.1/lib/generators/sidekiq/templates/worker_test.rb.erb0000644000175600017570000000033612631157272024230 0ustar pravipravirequire_relative 'test_helper' <% module_namespacing do -%> class <%= class_name %>WorkerTest < MiniTest::Unit::TestCase def test_example skip "add some examples to (or delete) #{__FILE__}" end end <% end -%>sidekiq-4.0.1/lib/sidekiq.rb0000644000175600017570000001254212631157272014744 0ustar pravipravi# encoding: utf-8 require 'sidekiq/version' fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby 1.9." if RUBY_PLATFORM != 'java' && RUBY_VERSION < '2.0.0' require 'sidekiq/logging' require 'sidekiq/client' require 'sidekiq/worker' require 'sidekiq/redis_connection' require 'json' module Sidekiq NAME = 'Sidekiq' LICENSE = 'See LICENSE and the LGPL-3.0 for licensing details.' DEFAULTS = { queues: [], labels: [], concurrency: 25, require: '.', environment: nil, timeout: 8, poll_interval_average: nil, average_scheduled_poll_interval: 15, error_handlers: [], lifecycle_events: { startup: [], quiet: [], shutdown: [], }, dead_max_jobs: 10_000, dead_timeout_in_seconds: 180 * 24 * 60 * 60 # 6 months } DEFAULT_WORKER_OPTIONS = { 'retry' => true, 'queue' => 'default' } def self.❨╯°□°❩╯︵┻━┻ puts "Calm down, yo." end def self.options @options ||= DEFAULTS.dup end def self.options=(opts) @options = opts end ## # Configuration for Sidekiq server, use like: # # Sidekiq.configure_server do |config| # config.redis = { :namespace => 'myapp', :size => 25, :url => 'redis://myhost:8877/0' } # config.server_middleware do |chain| # chain.add MyServerHook # end # end def self.configure_server yield self if server? end ## # Configuration for Sidekiq client, use like: # # Sidekiq.configure_client do |config| # config.redis = { :namespace => 'myapp', :size => 1, :url => 'redis://myhost:8877/0' } # end def self.configure_client yield self unless server? end def self.server? defined?(Sidekiq::CLI) end def self.redis raise ArgumentError, "requires a block" unless block_given? redis_pool.with do |conn| retryable = true begin yield conn rescue Redis::CommandError => ex #2550 Failover can cause the server to become a slave, need # to disconnect and reopen the socket to get back to the master. (conn.disconnect!; retryable = false; retry) if retryable && ex.message =~ /READONLY/ raise end end end def self.redis_pool @redis ||= Sidekiq::RedisConnection.create end def self.redis=(hash) @redis = if hash.is_a?(ConnectionPool) hash else Sidekiq::RedisConnection.create(hash) end end def self.client_middleware @client_chain ||= Middleware::Chain.new yield @client_chain if block_given? @client_chain end def self.server_middleware @server_chain ||= default_server_middleware yield @server_chain if block_given? @server_chain end def self.default_server_middleware require 'sidekiq/middleware/server/retry_jobs' require 'sidekiq/middleware/server/logging' Middleware::Chain.new do |m| m.add Middleware::Server::Logging m.add Middleware::Server::RetryJobs if defined?(::ActiveRecord::Base) require 'sidekiq/middleware/server/active_record' m.add Sidekiq::Middleware::Server::ActiveRecord end end end def self.default_worker_options=(hash) @default_worker_options = default_worker_options.merge(hash.stringify_keys) end def self.default_worker_options defined?(@default_worker_options) ? @default_worker_options : DEFAULT_WORKER_OPTIONS end def self.load_json(string) JSON.parse(string) end def self.dump_json(object) JSON.generate(object) end def self.logger Sidekiq::Logging.logger end def self.logger=(log) Sidekiq::Logging.logger = log end # How frequently Redis should be checked by a random Sidekiq process for # scheduled and retriable jobs. Each individual process will take turns by # waiting some multiple of this value. # # See sidekiq/scheduled.rb for an in-depth explanation of this value def self.average_scheduled_poll_interval=(interval) self.options[:average_scheduled_poll_interval] = interval end # Register a proc to handle any error which occurs within the Sidekiq process. # # Sidekiq.configure_server do |config| # config.error_handlers << proc {|ex,ctx_hash| MyErrorService.notify(ex, ctx_hash) } # end # # The default error handler logs errors to Sidekiq.logger. def self.error_handlers self.options[:error_handlers] end # Register a block to run at a point in the Sidekiq lifecycle. # :startup, :quiet or :shutdown are valid events. # # Sidekiq.configure_server do |config| # config.on(:shutdown) do # puts "Goodbye cruel world!" # end # end def self.on(event, &block) raise ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol) raise ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event) options[:lifecycle_events][event] << block end # We are shutting down Sidekiq but what about workers that # are working on some long job? This error is # raised in workers that have not finished within the hard # timeout limit. This is needed to rollback db transactions, # otherwise Ruby's Thread#kill will commit. See #377. # DO NOT RESCUE THIS ERROR IN YOUR WORKERS class Shutdown < Interrupt; end end require 'sidekiq/extensions/class_methods' require 'sidekiq/extensions/action_mailer' require 'sidekiq/extensions/active_record' require 'sidekiq/rails' if defined?(::Rails::Engine) sidekiq-4.0.1/lib/sidekiq/0000755000175600017570000000000012631157272014413 5ustar pravipravisidekiq-4.0.1/lib/sidekiq/fetch.rb0000644000175600017570000000432212631157272016032 0ustar pravipravirequire 'sidekiq' module Sidekiq class BasicFetch # We want the fetch operation to timeout every few seconds so the thread # can check if the process is shutting down. TIMEOUT = 2 UnitOfWork = Struct.new(:queue, :job) do def acknowledge # nothing to do end def queue_name queue.gsub(/.*queue:/, ''.freeze) end def requeue Sidekiq.redis do |conn| conn.rpush("queue:#{queue_name}", job) end end end def initialize(options) @strictly_ordered_queues = !!options[:strict] @queues = options[:queues].map { |q| "queue:#{q}" } if @strictly_ordered_queues @queues = @queues.uniq @queues << TIMEOUT end end def retrieve_work work = Sidekiq.redis { |conn| conn.brpop(*queues_cmd) } UnitOfWork.new(*work) if work end # Creating the Redis#brpop command takes into account any # configured queue weights. By default Redis#brpop returns # data from the first queue that has pending elements. We # recreate the queue command each time we invoke Redis#brpop # to honor weights and avoid queue starvation. def queues_cmd if @strictly_ordered_queues @queues else queues = @queues.shuffle.uniq queues << TIMEOUT queues end end # By leaving this as a class method, it can be pluggable and used by the Manager actor. Making it # an instance method will make it async to the Fetcher actor def self.bulk_requeue(inprogress, options) return if inprogress.empty? Sidekiq.logger.debug { "Re-queueing terminated jobs" } jobs_to_requeue = {} inprogress.each do |unit_of_work| jobs_to_requeue[unit_of_work.queue_name] ||= [] jobs_to_requeue[unit_of_work.queue_name] << unit_of_work.job end Sidekiq.redis do |conn| conn.pipelined do jobs_to_requeue.each do |queue, jobs| conn.rpush("queue:#{queue}", jobs) end end end Sidekiq.logger.info("Pushed #{inprogress.size} jobs back to Redis") rescue => ex Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}") end end end sidekiq-4.0.1/lib/sidekiq/version.rb0000644000175600017570000000004712631157272016426 0ustar pravipravimodule Sidekiq VERSION = "4.0.1" end sidekiq-4.0.1/lib/sidekiq/redis_connection.rb0000644000175600017570000000545612631157272020277 0ustar pravipravirequire 'connection_pool' require 'redis' require 'uri' module Sidekiq class RedisConnection class << self def create(options={}) options[:url] ||= determine_redis_provider size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 5) : 5) verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server? pool_timeout = options[:pool_timeout] || 1 log_info(options) ConnectionPool.new(:timeout => pool_timeout, :size => size) do build_client(options) end end private # Sidekiq needs a lot of concurrent Redis connections. # # We need a connection for each Processor. # We need a connection for Pro's real-time change listener # We need a connection to various features to call Redis every few seconds: # - the process heartbeat. # - enterprise's leader election # - enterprise's cron support def verify_sizing(size, concurrency) raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work, your pool has #{size} connections but really needs to have at least #{concurrency + 2}" if size <= concurrency end def build_client(options) namespace = options[:namespace] client = Redis.new client_opts(options) if namespace begin require 'redis/namespace' Redis::Namespace.new(namespace, :redis => client) rescue LoadError Sidekiq.logger.error("redis-namespace gem not included in Gemfile, cannot use namespace '#{namespace}'") exit(-127) end else client end end def client_opts(options) opts = options.dup if opts[:namespace] opts.delete(:namespace) end if opts[:network_timeout] opts[:timeout] = opts[:network_timeout] opts.delete(:network_timeout) end opts[:driver] = opts[:driver] || 'ruby' opts end def log_info(options) # Don't log Redis AUTH password redacted = "REDACTED" scrubbed_options = options.dup if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password uri.password = redacted scrubbed_options[:url] = uri.to_s end if scrubbed_options[:password] scrubbed_options[:password] = redacted end if Sidekiq.server? Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}") else Sidekiq.logger.debug("#{Sidekiq::NAME} client with redis options #{scrubbed_options}") end end def determine_redis_provider ENV[ENV['REDIS_PROVIDER'] || 'REDIS_URL'] end end end end sidekiq-4.0.1/lib/sidekiq/processor.rb0000644000175600017570000001057012631157272016762 0ustar pravipravirequire 'sidekiq/util' require 'sidekiq/fetch' require 'thread' require 'concurrent/map' require 'concurrent/atomic/atomic_fixnum' module Sidekiq ## # The Processor is a standalone thread which: # # 1. fetches a job from Redis # 2. executes the job # a. instantiate the Worker # b. run the middleware chain # c. call #perform # # A Processor can exit due to shutdown (processor_stopped) # or due to an error during job execution (processor_died) # # If an error occurs in the job execution, the # Processor calls the Manager to create a new one # to replace itself and exits. # class Processor include Util attr_reader :thread attr_reader :job def initialize(mgr) @mgr = mgr @down = false @done = false @job = nil @thread = nil @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options) end def terminate(wait=false) @done = true return if !@thread @thread.value if wait end def kill(wait=false) @done = true return if !@thread # unlike the other actors, terminate does not wait # for the thread to finish because we don't know how # long the job will take to finish. Instead we # provide a `kill` method to call after the shutdown # timeout passes. @thread.raise ::Sidekiq::Shutdown @thread.value if wait end def start @thread ||= safe_thread("processor", &method(:run)) end private unless $TESTING def run begin while !@done process_one end @mgr.processor_stopped(self) rescue Sidekiq::Shutdown @mgr.processor_stopped(self) rescue Exception => ex @mgr.processor_died(self, ex) end end def process_one @job = fetch process(@job) if @job @job = nil end def get_one begin work = @strategy.retrieve_work (logger.info { "Redis is online, #{Time.now - @down} sec downtime" }; @down = nil) if @down work rescue Sidekiq::Shutdown rescue => ex handle_fetch_exception(ex) end end def fetch j = get_one if j && @done j.requeue nil else j end end def handle_fetch_exception(ex) if !@down @down = Time.now logger.error("Error fetching job: #{ex}") ex.backtrace.each do |bt| logger.error(bt) end end sleep(1) end def process(work) jobstr = work.job queue = work.queue_name ack = false begin job = Sidekiq.load_json(jobstr) klass = job['class'.freeze].constantize worker = klass.new worker.jid = job['jid'.freeze] stats(worker, job, queue) do Sidekiq.server_middleware.invoke(worker, job, queue) do # Only ack if we either attempted to start this job or # successfully completed it. This prevents us from # losing jobs if a middleware raises an exception before yielding ack = true execute_job(worker, cloned(job['args'.freeze])) end end ack = true rescue Sidekiq::Shutdown # Had to force kill this job because it didn't finish # within the timeout. Don't acknowledge the work since # we didn't properly finish it. ack = false rescue Exception => ex handle_exception(ex, job || { :job => jobstr }) raise ensure work.acknowledge if ack end end def execute_job(worker, cloned_args) worker.perform(*cloned_args) end def thread_identity @str ||= Thread.current.object_id.to_s(36) end WORKER_STATE = Concurrent::Map.new PROCESSED = Concurrent::AtomicFixnum.new FAILURE = Concurrent::AtomicFixnum.new def stats(worker, job, queue) tid = thread_identity WORKER_STATE[tid] = {:queue => queue, :payload => job, :run_at => Time.now.to_i } begin yield rescue Exception FAILURE.increment raise ensure WORKER_STATE.delete(tid) PROCESSED.increment end end # Deep clone the arguments passed to the worker so that if # the job fails, what is pushed back onto Redis hasn't # been mutated by the worker. def cloned(ary) Marshal.load(Marshal.dump(ary)) end end end sidekiq-4.0.1/lib/sidekiq/testing.rb0000644000175600017570000001203712631157272016420 0ustar pravipravirequire 'securerandom' require 'sidekiq' module Sidekiq class Testing class << self attr_accessor :__test_mode def __set_test_mode(mode) if block_given? current_mode = self.__test_mode begin self.__test_mode = mode yield ensure self.__test_mode = current_mode end else self.__test_mode = mode end end def disable!(&block) __set_test_mode(:disable, &block) end def fake!(&block) __set_test_mode(:fake, &block) end def inline!(&block) __set_test_mode(:inline, &block) end def enabled? self.__test_mode != :disable end def disabled? self.__test_mode == :disable end def fake? self.__test_mode == :fake end def inline? self.__test_mode == :inline end def server_middleware @server_chain ||= Middleware::Chain.new yield @server_chain if block_given? @server_chain end end end # Default to fake testing to keep old behavior Sidekiq::Testing.fake! class EmptyQueueError < RuntimeError; end class Client alias_method :raw_push_real, :raw_push def raw_push(payloads) if Sidekiq::Testing.fake? payloads.each do |job| job['class'].constantize.jobs << Sidekiq.load_json(Sidekiq.dump_json(job)) end true elsif Sidekiq::Testing.inline? payloads.each do |job| job['jid'] ||= SecureRandom.hex(12) klass = job['class'].constantize klass.jobs.unshift Sidekiq.load_json(Sidekiq.dump_json(job)) klass.perform_one end true else raw_push_real(payloads) end end end module Worker ## # The Sidekiq testing infrastructure overrides perform_async # so that it does not actually touch the network. Instead it # stores the asynchronous jobs in a per-class array so that # their presence/absence can be asserted by your tests. # # This is similar to ActionMailer's :test delivery_method and its # ActionMailer::Base.deliveries array. # # Example: # # require 'sidekiq/testing' # # assert_equal 0, HardWorker.jobs.size # HardWorker.perform_async(:something) # assert_equal 1, HardWorker.jobs.size # assert_equal :something, HardWorker.jobs[0]['args'][0] # # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size # MyMailer.delay.send_welcome_email('foo@example.com') # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size # # You can also clear and drain all workers' jobs: # # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size # # MyMailer.delay.send_welcome_email('foo@example.com') # MyModel.delay.do_something_hard # # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size # assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size # # Sidekiq::Worker.clear_all # or .drain_all # # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size # # This can be useful to make sure jobs don't linger between tests: # # RSpec.configure do |config| # config.before(:each) do # Sidekiq::Worker.clear_all # end # end # # or for acceptance testing, i.e. with cucumber: # # AfterStep do # Sidekiq::Worker.drain_all # end # # When I sign up as "foo@example.com" # Then I should receive a welcome email to "foo@example.com" # module ClassMethods # Jobs queued for this worker def jobs Worker.jobs[self] end # Clear all jobs for this worker def clear jobs.clear end # Drain and run all jobs for this worker def drain while job = jobs.shift do process_job(job) end end # Pop out a single job and perform it def perform_one raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty? job = jobs.shift process_job(job) end def process_job(job) worker = new worker.jid = job['jid'] worker.bid = job['bid'] if worker.respond_to?(:bid=) Sidekiq::Testing.server_middleware.invoke(worker, job, job['queue']) do execute_job(worker, job['args']) end end def execute_job(worker, args) worker.perform(*args) end end class << self def jobs # :nodoc: @jobs ||= Hash.new { |hash, key| hash[key] = [] } end # Clear all queued jobs across all workers def clear_all jobs.clear end # Drain all queued jobs across all workers def drain_all until jobs.values.all?(&:empty?) do jobs.keys.each(&:drain) end end end end end sidekiq-4.0.1/lib/sidekiq/launcher.rb0000644000175600017570000001055512631157272016547 0ustar pravipravi# encoding: utf-8 require 'sidekiq/manager' require 'sidekiq/fetch' require 'sidekiq/scheduled' module Sidekiq # The Launcher is a very simple Actor whose job is to # start, monitor and stop the core Actors in Sidekiq. # If any of these actors die, the Sidekiq process exits # immediately. class Launcher include Util attr_accessor :manager, :poller, :fetcher def initialize(options) @manager = Sidekiq::Manager.new(options) @poller = Sidekiq::Scheduled::Poller.new @done = false @options = options end def run @thread = safe_thread("heartbeat", &method(:start_heartbeat)) @poller.start @manager.start end # Stops this instance from processing any more jobs, # def quiet @done = true @manager.quiet @poller.terminate end # Shuts down the process. This method does not # return until all work is complete and cleaned up. # It can take up to the timeout to complete. def stop deadline = Time.now + @options[:timeout] @done = true @manager.quiet @poller.terminate @manager.stop(deadline) # Requeue everything in case there was a worker who grabbed work while stopped # This call is a no-op in Sidekiq but necessary for Sidekiq Pro. strategy = (@options[:fetch] || Sidekiq::BasicFetch) strategy.bulk_requeue([], @options) clear_heartbeat end def stopping? @done end private unless $TESTING JVM_RESERVED_SIGNALS = ['USR1', 'USR2'] # Don't Process#kill if we get these signals via the API def heartbeat(k, data, json) results = Sidekiq::CLI::PROCTITLES.map {|x| x.(self, data) } results.compact! $0 = results.join(' ') ❤(k, json) end def ❤(key, json) fails = procd = 0 begin Processor::FAILURE.update {|curr| fails = curr; 0 } Processor::PROCESSED.update {|curr| procd = curr; 0 } workers_key = "#{key}:workers".freeze nowdate = Time.now.utc.strftime("%Y-%m-%d".freeze) Sidekiq.redis do |conn| conn.pipelined do conn.incrby("stat:processed".freeze, procd) conn.incrby("stat:processed:#{nowdate}", procd) conn.incrby("stat:failed".freeze, fails) conn.incrby("stat:failed:#{nowdate}", fails) conn.del(workers_key) Processor::WORKER_STATE.each_pair do |tid, hash| conn.hset(workers_key, tid, Sidekiq.dump_json(hash)) end end end fails = procd = 0 _, _, _, msg = Sidekiq.redis do |conn| conn.pipelined do conn.sadd('processes', key) conn.hmset(key, 'info', json, 'busy', Processor::WORKER_STATE.size, 'beat', Time.now.to_f) conn.expire(key, 60) conn.rpop("#{key}-signals") end end return unless msg if JVM_RESERVED_SIGNALS.include?(msg) Sidekiq::CLI.instance.handle_signal(msg) else ::Process.kill(msg, $$) end rescue => e # ignore all redis/network issues logger.error("heartbeat: #{e.message}") # don't lose the counts if there was a network issue Processor::PROCESSED.increment(procd) Processor::FAILURE.increment(fails) end end def start_heartbeat k = identity data = { 'hostname' => hostname, 'started_at' => Time.now.to_f, 'pid' => $$, 'tag' => @options[:tag] || '', 'concurrency' => @options[:concurrency], 'queues' => @options[:queues].uniq, 'labels' => @options[:labels], 'identity' => k, } # this data doesn't change so dump it to a string # now so we don't need to dump it every heartbeat. json = Sidekiq.dump_json(data) while true heartbeat(k, data, json) sleep 5 end Sidekiq.logger.info("Heartbeat stopping...") end def clear_heartbeat # Remove record from Redis since we are shutting down. # Note we don't stop the heartbeat thread; if the process # doesn't actually exit, it'll reappear in the Web UI. Sidekiq.redis do |conn| conn.pipelined do conn.srem('processes', identity) conn.del("#{identity}:workers") end end rescue # best effort, ignore network errors end end end sidekiq-4.0.1/lib/sidekiq/rails.rb0000644000175600017570000000205112631157272016050 0ustar pravipravimodule Sidekiq def self.hook_rails! return if defined?(@delay_removed) ActiveSupport.on_load(:active_record) do include Sidekiq::Extensions::ActiveRecord end ActiveSupport.on_load(:action_mailer) do extend Sidekiq::Extensions::ActionMailer end Module.__send__(:include, Sidekiq::Extensions::Klass) end # Removes the generic aliases which MAY clash with names of already # created methods by other applications. The methods `sidekiq_delay`, # `sidekiq_delay_for` and `sidekiq_delay_until` can be used instead. def self.remove_delay! @delay_removed = true [Extensions::ActiveRecord, Extensions::ActionMailer, Extensions::Klass].each do |mod| mod.module_eval do remove_method :delay if respond_to?(:delay) remove_method :delay_for if respond_to?(:delay_for) remove_method :delay_until if respond_to?(:delay_until) end end end class Rails < ::Rails::Engine initializer 'sidekiq' do Sidekiq.hook_rails! end end if defined?(::Rails) end sidekiq-4.0.1/lib/sidekiq/extensions/0000755000175600017570000000000012631157272016612 5ustar pravipravisidekiq-4.0.1/lib/sidekiq/extensions/class_methods.rb0000644000175600017570000000221212631157272021764 0ustar pravipravirequire 'sidekiq/extensions/generic_proxy' module Sidekiq module Extensions ## # Adds 'delay', 'delay_for' and `delay_until` methods to all Classes to offload class method # execution to Sidekiq. Examples: # # User.delay.delete_inactive # Wikipedia.delay.download_changes_for(Date.today) # class DelayedClass include Sidekiq::Worker def perform(yml) (target, method_name, args) = YAML.load(yml) target.__send__(method_name, *args) end end module Klass def sidekiq_delay(options={}) Proxy.new(DelayedClass, self, options) end def sidekiq_delay_for(interval, options={}) Proxy.new(DelayedClass, self, options.merge('at' => Time.now.to_f + interval.to_f)) end def sidekiq_delay_until(timestamp, options={}) Proxy.new(DelayedClass, self, options.merge('at' => timestamp.to_f)) end alias_method :delay, :sidekiq_delay alias_method :delay_for, :sidekiq_delay_for alias_method :delay_until, :sidekiq_delay_until end end end Module.__send__(:include, Sidekiq::Extensions::Klass) unless defined?(::Rails) sidekiq-4.0.1/lib/sidekiq/extensions/generic_proxy.rb0000644000175600017570000000134512631157272022017 0ustar pravipravirequire 'yaml' module Sidekiq module Extensions class Proxy < BasicObject def initialize(performable, target, options={}) @performable = performable @target = target @opts = options end def method_missing(name, *args) # Sidekiq has a limitation in that its message must be JSON. # JSON can't round trip real Ruby objects so we use YAML to # serialize the objects to a String. The YAML will be converted # to JSON and then deserialized on the other side back into a # Ruby object. obj = [@target, name, args] @performable.client_push({ 'class' => @performable, 'args' => [::YAML.dump(obj)] }.merge(@opts)) end end end end sidekiq-4.0.1/lib/sidekiq/extensions/active_record.rb0000644000175600017570000000242712631157272021755 0ustar pravipravirequire 'sidekiq/extensions/generic_proxy' module Sidekiq module Extensions ## # Adds 'delay', 'delay_for' and `delay_until` methods to ActiveRecord to offload instance method # execution to Sidekiq. Examples: # # User.recent_signups.each { |user| user.delay.mark_as_awesome } # # Please note, this is not recommended as this will serialize the entire # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances. # This is here for backwards compatibility with Delayed::Job only. class DelayedModel include Sidekiq::Worker def perform(yml) (target, method_name, args) = YAML.load(yml) target.__send__(method_name, *args) end end module ActiveRecord def sidekiq_delay(options={}) Proxy.new(DelayedModel, self, options) end def sidekiq_delay_for(interval, options={}) Proxy.new(DelayedModel, self, options.merge('at' => Time.now.to_f + interval.to_f)) end def sidekiq_delay_until(timestamp, options={}) Proxy.new(DelayedModel, self, options.merge('at' => timestamp.to_f)) end alias_method :delay, :sidekiq_delay alias_method :delay_for, :sidekiq_delay_for alias_method :delay_until, :sidekiq_delay_until end end end sidekiq-4.0.1/lib/sidekiq/extensions/action_mailer.rb0000644000175600017570000000324312631157272021747 0ustar pravipravirequire 'sidekiq/extensions/generic_proxy' module Sidekiq module Extensions ## # Adds 'delay', 'delay_for' and `delay_until` methods to ActionMailer to offload arbitrary email # delivery to Sidekiq. Example: # # UserMailer.delay.send_welcome_email(new_user) # UserMailer.delay_for(5.days).send_welcome_email(new_user) # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user) class DelayedMailer include Sidekiq::Worker def perform(yml) (target, method_name, args) = YAML.load(yml) msg = target.public_send(method_name, *args) # The email method can return nil, which causes ActionMailer to return # an undeliverable empty message. if msg deliver(msg) else raise "#{target.name}##{method_name} returned an undeliverable mail object" end end private def deliver(msg) if msg.respond_to?(:deliver_now) # Rails 4.2/5.0 msg.deliver_now else # Rails 3.2/4.0/4.1 msg.deliver end end end module ActionMailer def sidekiq_delay(options={}) Proxy.new(DelayedMailer, self, options) end def sidekiq_delay_for(interval, options={}) Proxy.new(DelayedMailer, self, options.merge('at' => Time.now.to_f + interval.to_f)) end def sidekiq_delay_until(timestamp, options={}) Proxy.new(DelayedMailer, self, options.merge('at' => timestamp.to_f)) end alias_method :delay, :sidekiq_delay alias_method :delay_for, :sidekiq_delay_for alias_method :delay_until, :sidekiq_delay_until end end end sidekiq-4.0.1/lib/sidekiq/api.rb0000644000175600017570000004552012631157272015517 0ustar pravipravi# encoding: utf-8 require 'sidekiq' module Sidekiq class Stats def initialize fetch_stats! end def processed stat :processed end def failed stat :failed end def scheduled_size stat :scheduled_size end def retry_size stat :retry_size end def dead_size stat :dead_size end def enqueued stat :enqueued end def processes_size stat :processes_size end def workers_size stat :workers_size end def default_queue_latency stat :default_queue_latency end def queues Sidekiq::Stats::Queues.new.lengths end def fetch_stats! pipe1_res = Sidekiq.redis do |conn| conn.pipelined do conn.get('stat:processed'.freeze) conn.get('stat:failed'.freeze) conn.zcard('schedule'.freeze) conn.zcard('retry'.freeze) conn.zcard('dead'.freeze) conn.scard('processes'.freeze) conn.lrange('queue:default'.freeze, -1, -1) conn.smembers('processes'.freeze) conn.smembers('queues'.freeze) end end pipe2_res = Sidekiq.redis do |conn| conn.pipelined do pipe1_res[7].each {|key| conn.hget(key, 'busy'.freeze) } pipe1_res[8].each {|queue| conn.llen("queue:#{queue}") } end end s = pipe1_res[7].size workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+) enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+) default_queue_latency = if (entry = pipe1_res[6].first) Time.now.to_f - Sidekiq.load_json(entry)['enqueued_at'.freeze] else 0 end @stats = { processed: pipe1_res[0].to_i, failed: pipe1_res[1].to_i, scheduled_size: pipe1_res[2], retry_size: pipe1_res[3], dead_size: pipe1_res[4], processes_size: pipe1_res[5], default_queue_latency: default_queue_latency, workers_size: workers_size, enqueued: enqueued } end def reset(*stats) all = %w(failed processed) stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s) mset_args = [] stats.each do |stat| mset_args << "stat:#{stat}" mset_args << 0 end Sidekiq.redis do |conn| conn.mset(*mset_args) end end private def stat(s) @stats[s] end class Queues def lengths Sidekiq.redis do |conn| queues = conn.smembers('queues'.freeze) lengths = conn.pipelined do queues.each do |queue| conn.llen("queue:#{queue}") end end i = 0 array_of_arrays = queues.inject({}) do |memo, queue| memo[queue] = lengths[i] i += 1 memo end.sort_by { |_, size| size } Hash[array_of_arrays.reverse] end end end class History def initialize(days_previous, start_date = nil) @days_previous = days_previous @start_date = start_date || Time.now.utc.to_date end def processed date_stat_hash("processed") end def failed date_stat_hash("failed") end private def date_stat_hash(stat) i = 0 stat_hash = {} keys = [] dates = [] while i < @days_previous date = @start_date - i datestr = date.strftime("%Y-%m-%d".freeze) keys << "stat:#{stat}:#{datestr}" dates << datestr i += 1 end Sidekiq.redis do |conn| conn.mget(keys).each_with_index do |value, idx| stat_hash[dates[idx]] = value ? value.to_i : 0 end end stat_hash end end end ## # Encapsulates a queue within Sidekiq. # Allows enumeration of all jobs within the queue # and deletion of jobs. # # queue = Sidekiq::Queue.new("mailer") # queue.each do |job| # job.klass # => 'MyWorker' # job.args # => [1, 2, 3] # job.delete if job.jid == 'abcdef1234567890' # end # class Queue include Enumerable def self.all Sidekiq.redis {|c| c.smembers('queues'.freeze) }.sort.map {|q| Sidekiq::Queue.new(q) } end attr_reader :name def initialize(name="default") @name = name @rname = "queue:#{name}" end def size Sidekiq.redis { |con| con.llen(@rname) } end # Sidekiq Pro overrides this def paused? false end def latency entry = Sidekiq.redis do |conn| conn.lrange(@rname, -1, -1) end.first return 0 unless entry Time.now.to_f - Sidekiq.load_json(entry)['enqueued_at'] end def each initial_size = size deleted_size = 0 page = 0 page_size = 50 while true do range_start = page * page_size - deleted_size range_end = page * page_size - deleted_size + (page_size - 1) entries = Sidekiq.redis do |conn| conn.lrange @rname, range_start, range_end end break if entries.empty? page += 1 entries.each do |entry| yield Job.new(entry, @name) end deleted_size = initial_size - size end end def find_job(jid) detect { |j| j.jid == jid } end def clear Sidekiq.redis do |conn| conn.multi do conn.del(@rname) conn.srem("queues".freeze, name) end end end alias_method :💣, :clear end ## # Encapsulates a pending job within a Sidekiq queue or # sorted set. # # The job should be considered immutable but may be # removed from the queue via Job#delete. # class Job attr_reader :item def initialize(item, queue_name=nil) @value = item @item = item.is_a?(Hash) ? item : Sidekiq.load_json(item) @queue = queue_name || @item['queue'] end def klass @item['class'] end def display_class # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI @klass ||= case klass when /\ASidekiq::Extensions::Delayed/ safe_load(args[0], klass) do |target, method, _| "#{target}.#{method}" end when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" @item['wrapped'] || args[0] else klass end end def display_args # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI @args ||= case klass when /\ASidekiq::Extensions::Delayed/ safe_load(args[0], args) do |_, _, arg| arg end when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" @item['wrapped'] ? args[0]["arguments"] : [] else args end end def args @item['args'] end def jid @item['jid'] end def enqueued_at @item['enqueued_at'] ? Time.at(@item['enqueued_at']).utc : nil end def created_at Time.at(@item['created_at'] || @item['enqueued_at'] || 0).utc end def queue @queue end def latency Time.now.to_f - (@item['enqueued_at'] || @item['created_at']) end ## # Remove this job from the queue. def delete count = Sidekiq.redis do |conn| conn.lrem("queue:#{@queue}", 1, @value) end count != 0 end def [](name) @item.__send__(:[], name) end private def safe_load(content, default) begin yield(*YAML.load(content)) rescue => ex # #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into # memory yet so the YAML can't be loaded. Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.options[:environment] == 'development' default end end end class SortedEntry < Job attr_reader :score attr_reader :parent def initialize(parent, score, item) super(item) @score = score @parent = parent end def at Time.at(score).utc end def delete if @value @parent.delete_by_value(@parent.name, @value) else @parent.delete_by_jid(score, jid) end end def reschedule(at) delete @parent.schedule(at, item) end def add_to_queue remove_job do |message| msg = Sidekiq.load_json(message) Sidekiq::Client.push(msg) end end def retry raise "Retry not available on jobs which have not failed" unless item["failed_at"] remove_job do |message| msg = Sidekiq.load_json(message) msg['retry_count'] -= 1 Sidekiq::Client.push(msg) end end ## # Place job in the dead set def kill raise 'Kill not available on jobs which have not failed' unless item['failed_at'] remove_job do |message| Sidekiq.logger.info { "Killing job #{message['jid']}" } now = Time.now.to_f Sidekiq.redis do |conn| conn.multi do conn.zadd('dead', now, message) conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout) conn.zremrangebyrank('dead', 0, - DeadSet.max_jobs) end end end end private def remove_job Sidekiq.redis do |conn| results = conn.multi do conn.zrangebyscore(parent.name, score, score) conn.zremrangebyscore(parent.name, score, score) end.first if results.size == 1 yield results.first else # multiple jobs with the same score # find the one with the right JID and push it hash = results.group_by do |message| if message.index(jid) msg = Sidekiq.load_json(message) msg['jid'] == jid else false end end msg = hash.fetch(true, []).first yield msg if msg # push the rest back onto the sorted set conn.multi do hash.fetch(false, []).each do |message| conn.zadd(parent.name, score.to_f.to_s, message) end end end end end end class SortedSet include Enumerable attr_reader :name def initialize(name) @name = name @_size = size end def size Sidekiq.redis { |c| c.zcard(name) } end def clear Sidekiq.redis do |conn| conn.del(name) end end alias_method :💣, :clear end class JobSet < SortedSet def schedule(timestamp, message) Sidekiq.redis do |conn| conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(message)) end end def each initial_size = @_size offset_size = 0 page = -1 page_size = 50 while true do range_start = page * page_size + offset_size range_end = page * page_size + offset_size + (page_size - 1) elements = Sidekiq.redis do |conn| conn.zrange name, range_start, range_end, with_scores: true end break if elements.empty? page -= 1 elements.each do |element, score| yield SortedEntry.new(self, score, element) end offset_size = initial_size - @_size end end def fetch(score, jid = nil) elements = Sidekiq.redis do |conn| conn.zrangebyscore(name, score, score) end elements.inject([]) do |result, element| entry = SortedEntry.new(self, score, element) if jid result << entry if entry.jid == jid else result << entry end result end end def find_job(jid) self.detect { |j| j.jid == jid } end def delete_by_value(name, value) Sidekiq.redis do |conn| ret = conn.zrem(name, value) @_size -= 1 if ret ret end end def delete_by_jid(score, jid) Sidekiq.redis do |conn| elements = conn.zrangebyscore(name, score, score) elements.each do |element| message = Sidekiq.load_json(element) if message["jid"] == jid ret = conn.zrem(name, element) @_size -= 1 if ret break ret end false end end end alias_method :delete, :delete_by_jid end ## # Allows enumeration of scheduled jobs within Sidekiq. # Based on this, you can search/filter for jobs. Here's an # example where I'm selecting all jobs of a certain type # and deleting them from the retry queue. # # r = Sidekiq::ScheduledSet.new # r.select do |retri| # retri.klass == 'Sidekiq::Extensions::DelayedClass' && # retri.args[0] == 'User' && # retri.args[1] == 'setup_new_subscriber' # end.map(&:delete) class ScheduledSet < JobSet def initialize super 'schedule' end end ## # Allows enumeration of retries within Sidekiq. # Based on this, you can search/filter for jobs. Here's an # example where I'm selecting all jobs of a certain type # and deleting them from the retry queue. # # r = Sidekiq::RetrySet.new # r.select do |retri| # retri.klass == 'Sidekiq::Extensions::DelayedClass' && # retri.args[0] == 'User' && # retri.args[1] == 'setup_new_subscriber' # end.map(&:delete) class RetrySet < JobSet def initialize super 'retry' end def retry_all while size > 0 each(&:retry) end end end ## # Allows enumeration of dead jobs within Sidekiq. # class DeadSet < JobSet def initialize super 'dead' end def retry_all while size > 0 each(&:retry) end end def self.max_jobs Sidekiq.options[:dead_max_jobs] end def self.timeout Sidekiq.options[:dead_timeout_in_seconds] end end ## # Enumerates the set of Sidekiq processes which are actively working # right now. Each process send a heartbeat to Redis every 5 seconds # so this set should be relatively accurate, barring network partitions. # # Yields a Sidekiq::Process. # class ProcessSet include Enumerable def initialize(clean_plz=true) self.class.cleanup if clean_plz end # Cleans up dead processes recorded in Redis. # Returns the number of processes cleaned. def self.cleanup count = 0 Sidekiq.redis do |conn| procs = conn.smembers('processes').sort heartbeats = conn.pipelined do procs.each do |key| conn.hget(key, 'info') end end # the hash named key has an expiry of 60 seconds. # if it's not found, that means the process has not reported # in to Redis and probably died. to_prune = [] heartbeats.each_with_index do |beat, i| to_prune << procs[i] if beat.nil? end count = conn.srem('processes', to_prune) unless to_prune.empty? end count end def each procs = Sidekiq.redis { |conn| conn.smembers('processes') }.sort Sidekiq.redis do |conn| # We're making a tradeoff here between consuming more memory instead of # making more roundtrips to Redis, but if you have hundreds or thousands of workers, # you'll be happier this way result = conn.pipelined do procs.each do |key| conn.hmget(key, 'info', 'busy', 'beat') end end result.each do |info, busy, at_s| hash = Sidekiq.load_json(info) yield Process.new(hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f)) end end nil end # This method is not guaranteed accurate since it does not prune the set # based on current heartbeat. #each does that and ensures the set only # contains Sidekiq processes which have sent a heartbeat within the last # 60 seconds. def size Sidekiq.redis { |conn| conn.scard('processes') } end end # # Sidekiq::Process has a set of attributes which look like this: # # { # 'hostname' => 'app-1.example.com', # 'started_at' => , # 'pid' => 12345, # 'tag' => 'myapp' # 'concurrency' => 25, # 'queues' => ['default', 'low'], # 'busy' => 10, # 'beat' => , # 'identity' => , # } class Process def initialize(hash) @attribs = hash end def tag self['tag'] end def labels Array(self['labels']) end def [](key) @attribs[key] end def quiet! signal('USR1') end def stop! signal('TERM') end def dump_threads signal('TTIN') end private def signal(sig) key = "#{identity}-signals" Sidekiq.redis do |c| c.multi do c.lpush(key, sig) c.expire(key, 60) end end end def identity self['identity'] end end ## # Programmatic access to the current active worker set. # # WARNING WARNING WARNING # # This is live data that can change every millisecond. # If you call #size => 5 and then expect #each to be # called 5 times, you're going to have a bad time. # # workers = Sidekiq::Workers.new # workers.size => 2 # workers.each do |process_id, thread_id, work| # # process_id is a unique identifier per Sidekiq process # # thread_id is a unique identifier per thread # # work is a Hash which looks like: # # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg } # # run_at is an epoch Integer. # end # class Workers include Enumerable def each Sidekiq.redis do |conn| procs = conn.smembers('processes') procs.sort.each do |key| valid, workers = conn.pipelined do conn.exists(key) conn.hgetall("#{key}:workers") end next unless valid workers.each_pair do |tid, json| yield key, tid, Sidekiq.load_json(json) end end end end # Note that #size is only as accurate as Sidekiq's heartbeat, # which happens every 5 seconds. It is NOT real-time. # # Not very efficient if you have lots of Sidekiq # processes but the alternative is a global counter # which can easily get out of sync with crashy processes. def size Sidekiq.redis do |conn| procs = conn.smembers('processes') if procs.empty? 0 else conn.pipelined do procs.each do |key| conn.hget(key, 'busy') end end.map(&:to_i).inject(:+) end end end end end sidekiq-4.0.1/lib/sidekiq/cli.rb0000644000175600017570000002600412631157272015511 0ustar pravipravi# encoding: utf-8 $stdout.sync = true require 'yaml' require 'singleton' require 'optparse' require 'erb' require 'fileutils' require 'sidekiq' require 'sidekiq/util' module Sidekiq class CLI include Util include Singleton unless $TESTING PROCTITLES = [ proc { 'sidekiq'.freeze }, proc { Sidekiq::VERSION }, proc { |me, data| data['tag'] }, proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data['concurrency']} busy]" }, proc { |me, data| "stopping" if me.stopping? }, ] # Used for CLI testing attr_accessor :code attr_accessor :launcher attr_accessor :environment def initialize @code = nil end def parse(args=ARGV) @code = nil setup_options(args) initialize_logger validate! daemonize write_pid end # Code within this method is not tested because it alters # global process state irreversibly. PRs which improve the # test coverage of Sidekiq::CLI are welcomed. def run boot_system print_banner self_read, self_write = IO.pipe %w(INT TERM USR1 USR2 TTIN).each do |sig| begin trap sig do self_write.puts(sig) end rescue ArgumentError puts "Signal #{sig} not supported" end end logger.info "Running in #{RUBY_DESCRIPTION}" logger.info Sidekiq::LICENSE logger.info "Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org" unless defined?(::Sidekiq::Pro) Sidekiq.redis do |conn| # touch the connection pool so it is created before we # fire startup and start multithreading. ver = conn.info['redis_version'] raise "You are using Redis v#{ver}, Sidekiq requires Redis v2.8.0 or greater" if ver < '2.8' end # Before this point, the process is initializing with just the main thread. # Starting here the process will now have multiple threads running. fire_event(:startup) logger.debug { "Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" } if !options[:daemon] logger.info 'Starting processing, hit Ctrl-C to stop' end require 'sidekiq/launcher' @launcher = Sidekiq::Launcher.new(options) begin launcher.run while readable_io = IO.select([self_read]) signal = readable_io.first[0].gets.strip handle_signal(signal) end rescue Interrupt logger.info 'Shutting down' launcher.stop # Explicitly exit so busy Processor threads can't block # process shutdown. logger.info "Bye!" exit(0) end end def self.banner %q{ m, `$b .ss, $$: .,d$ `$$P,d$P' .,md$P"' ,$$$$$bmmd$$$P^' .d$$$$$$$$$$P' $$^' `"^$$$' ____ _ _ _ _ $: ,$$: / ___|(_) __| | ___| | _(_) __ _ `b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` | $$: ___) | | (_| | __/ <| | (_| | $$ |____/|_|\__,_|\___|_|\_\_|\__, | .d$$ |_| } end def handle_signal(sig) Sidekiq.logger.debug "Got #{sig} signal" case sig when 'INT' # Handle Ctrl-C in JRuby like MRI # http://jira.codehaus.org/browse/JRUBY-4637 raise Interrupt when 'TERM' # Heroku sends TERM and then waits 10 seconds for process to exit. raise Interrupt when 'USR1' Sidekiq.logger.info "Received USR1, no longer accepting new work" launcher.quiet when 'USR2' if Sidekiq.options[:logfile] Sidekiq.logger.info "Received USR2, reopening log file" Sidekiq::Logging.reopen_logs end when 'TTIN' Thread.list.each do |thread| Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" if thread.backtrace Sidekiq.logger.warn thread.backtrace.join("\n") else Sidekiq.logger.warn "" end end end end private def print_banner # Print logo and banner for development if environment == 'development' && $stdout.tty? puts "\e[#{31}m" puts Sidekiq::CLI.banner puts "\e[0m" end end def daemonize return unless options[:daemon] raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile] files_to_reopen = [] ObjectSpace.each_object(File) do |file| files_to_reopen << file unless file.closed? end ::Process.daemon(true, true) files_to_reopen.each do |file| begin file.reopen file.path, "a+" file.sync = true rescue ::Exception end end [$stdout, $stderr].each do |io| File.open(options[:logfile], 'ab') do |f| io.reopen(f) end io.sync = true end $stdin.reopen('/dev/null') initialize_logger end def set_environment(cli_env) @environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development' end alias_method :die, :exit alias_method :☠, :exit def setup_options(args) opts = parse_options(args) set_environment opts[:environment] cfile = opts[:config_file] opts = parse_config(cfile).merge(opts) if cfile opts[:strict] = true if opts[:strict].nil? options.merge!(opts) end def options Sidekiq.options end def boot_system ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require]) if File.directory?(options[:require]) require 'rails' if ::Rails::VERSION::MAJOR < 4 require 'sidekiq/rails' require File.expand_path("#{options[:require]}/config/environment.rb") ::Rails.application.eager_load! else # Painful contortions, see 1791 for discussion require File.expand_path("#{options[:require]}/config/application.rb") ::Rails::Application.initializer "sidekiq.eager_load" do ::Rails.application.config.eager_load = true end require 'sidekiq/rails' require File.expand_path("#{options[:require]}/config/environment.rb") end options[:tag] ||= default_tag else require options[:require] end end def default_tag dir = ::Rails.root name = File.basename(dir) if name.to_i != 0 && prevdir = File.dirname(dir) # Capistrano release directory? if File.basename(prevdir) == 'releases' return File.basename(File.dirname(prevdir)) end end name end def validate! options[:queues] << 'default' if options[:queues].empty? if !File.exist?(options[:require]) || (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb")) logger.info "==================================================================" logger.info " Please point sidekiq to a Rails 3/4 application or a Ruby file " logger.info " to load your worker classes with -r [DIR|FILE]." logger.info "==================================================================" logger.info @parser die(1) end [:concurrency, :timeout].each do |opt| raise ArgumentError, "#{opt}: #{options[opt]} is not a valid value" if options.has_key?(opt) && options[opt].to_i <= 0 end end def parse_options(argv) opts = {} @parser = OptionParser.new do |o| o.on '-c', '--concurrency INT', "processor threads to use" do |arg| opts[:concurrency] = Integer(arg) end o.on '-d', '--daemon', "Daemonize process" do |arg| opts[:daemon] = arg end o.on '-e', '--environment ENV', "Application environment" do |arg| opts[:environment] = arg end o.on '-g', '--tag TAG', "Process tag for procline" do |arg| opts[:tag] = arg end o.on '-i', '--index INT', "unique process index on this machine" do |arg| opts[:index] = Integer(arg.match(/\d+/)[0]) end o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg| queue, weight = arg.split(",") parse_queue opts, queue, weight end o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg| opts[:require] = arg end o.on '-t', '--timeout NUM', "Shutdown timeout" do |arg| opts[:timeout] = Integer(arg) end o.on "-v", "--verbose", "Print more verbose output" do |arg| opts[:verbose] = arg end o.on '-C', '--config PATH', "path to YAML config file" do |arg| opts[:config_file] = arg end o.on '-L', '--logfile PATH', "path to writable logfile" do |arg| opts[:logfile] = arg end o.on '-P', '--pidfile PATH', "path to pidfile" do |arg| opts[:pidfile] = arg end o.on '-V', '--version', "Print version and exit" do |arg| puts "Sidekiq #{Sidekiq::VERSION}" die(0) end end @parser.banner = "sidekiq [options]" @parser.on_tail "-h", "--help", "Show help" do logger.info @parser die 1 end @parser.parse!(argv) %w[config/sidekiq.yml config/sidekiq.yml.erb].each do |filename| opts[:config_file] ||= filename if File.exist?(filename) end opts end def initialize_logger Sidekiq::Logging.initialize_logger(options[:logfile]) if options[:logfile] Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose] end def write_pid if path = options[:pidfile] pidfile = File.expand_path(path) File.open(pidfile, 'w') do |f| f.puts ::Process.pid end end end def parse_config(cfile) opts = {} if File.exist?(cfile) opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts opts = opts.merge(opts.delete(environment) || {}) parse_queues(opts, opts.delete(:queues) || []) else # allow a non-existent config file so Sidekiq # can be deployed by cap with just the defaults. end ns = opts.delete(:namespace) if ns # logger hasn't been initialized yet, puts is all we have. puts("namespace should be set in your ruby initializer, is ignored in config file") puts("config.redis = { :url => ..., :namespace => '#{ns}' }") end opts end def parse_queues(opts, queues_and_weights) queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) } end def parse_queue(opts, q, weight=nil) [weight.to_i, 1].max.times do (opts[:queues] ||= []) << q end opts[:strict] = false if weight.to_i > 0 end end end sidekiq-4.0.1/lib/sidekiq/client.rb0000644000175600017570000002020312631157272016213 0ustar pravipravirequire 'securerandom' require 'sidekiq/middleware/chain' module Sidekiq class Client ## # Define client-side middleware: # # client = Sidekiq::Client.new # client.middleware do |chain| # chain.use MyClientMiddleware # end # client.push('class' => 'SomeWorker', 'args' => [1,2,3]) # # All client instances default to the globally-defined # Sidekiq.client_middleware but you can change as necessary. # def middleware(&block) @chain ||= Sidekiq.client_middleware if block_given? @chain = @chain.dup yield @chain end @chain end attr_accessor :redis_pool # Sidekiq::Client normally uses the default Redis pool but you may # pass a custom ConnectionPool if you want to shard your # Sidekiq jobs across several Redis instances (for scalability # reasons, e.g.) # # Sidekiq::Client.new(ConnectionPool.new { Redis.new }) # # Generally this is only needed for very large Sidekiq installs processing # more than thousands jobs per second. I do not recommend sharding unless # you truly cannot scale any other way (e.g. splitting your app into smaller apps). # Some features, like the API, do not support sharding: they are designed to work # against a single Redis instance only. def initialize(redis_pool=nil) @redis_pool = redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool end ## # The main method used to push a job to Redis. Accepts a number of options: # # queue - the named queue to use, default 'default' # class - the worker class to call, required # args - an array of simple arguments to the perform method, must be JSON-serializable # retry - whether to retry this job if it fails, true or false, default true # backtrace - whether to save any error backtrace, default false # # All options must be strings, not symbols. NB: because we are serializing to JSON, all # symbols in 'args' will be converted to strings. # # Returns a unique Job ID. If middleware stops the job, nil will be returned instead. # # Example: # push('queue' => 'my_queue', 'class' => MyWorker, 'args' => ['foo', 1, :bat => 'bar']) # def push(item) normed = normalize_item(item) payload = process_single(item['class'], normed) if payload raw_push([payload]) payload['jid'] end end ## # Push a large number of jobs to Redis. In practice this method is only # useful if you are pushing tens of thousands of jobs or more, or if you need # to ensure that a batch doesn't complete prematurely. This method # basically cuts down on the redis round trip latency. # # Takes the same arguments as #push except that args is expected to be # an Array of Arrays. All other keys are duplicated for each job. Each job # is run through the client middleware pipeline and each job gets its own Job ID # as normal. # # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less # than the number given if the middleware stopped processing for one or more jobs. def push_bulk(items) normed = normalize_item(items) payloads = items['args'].map do |args| raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" if !args.is_a?(Array) process_single(items['class'], normed.merge('args' => args, 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f)) end.compact raw_push(payloads) if !payloads.empty? payloads.collect { |payload| payload['jid'] } end # Allows sharding of jobs across any number of Redis instances. All jobs # defined within the block will use the given Redis connection pool. # # pool = ConnectionPool.new { Redis.new } # Sidekiq::Client.via(pool) do # SomeWorker.perform_async(1,2,3) # SomeOtherWorker.perform_async(1,2,3) # end # # Generally this is only needed for very large Sidekiq installs processing # more than thousands jobs per second. I do not recommend sharding unless # you truly cannot scale any other way (e.g. splitting your app into smaller apps). # Some features, like the API, do not support sharding: they are designed to work # against a single Redis instance. def self.via(pool) raise ArgumentError, "No pool given" if pool.nil? raise RuntimeError, "Sidekiq::Client.via is not re-entrant" if x = Thread.current[:sidekiq_via_pool] && x != pool Thread.current[:sidekiq_via_pool] = pool yield ensure Thread.current[:sidekiq_via_pool] = nil end class << self def push(item) new.push(item) end def push_bulk(items) new.push_bulk(items) end # Resque compatibility helpers. Note all helpers # should go through Worker#client_push. # # Example usage: # Sidekiq::Client.enqueue(MyWorker, 'foo', 1, :bat => 'bar') # # Messages are enqueued to the 'default' queue. # def enqueue(klass, *args) klass.client_push('class' => klass, 'args' => args) end # Example usage: # Sidekiq::Client.enqueue_to(:queue_name, MyWorker, 'foo', 1, :bat => 'bar') # def enqueue_to(queue, klass, *args) klass.client_push('queue' => queue, 'class' => klass, 'args' => args) end # Example usage: # Sidekiq::Client.enqueue_to_in(:queue_name, 3.minutes, MyWorker, 'foo', 1, :bat => 'bar') # def enqueue_to_in(queue, interval, klass, *args) int = interval.to_f now = Time.now.to_f ts = (int < 1_000_000_000 ? now + int : int) item = { 'class' => klass, 'args' => args, 'at' => ts, 'queue' => queue } item.delete('at'.freeze) if ts <= now klass.client_push(item) end # Example usage: # Sidekiq::Client.enqueue_in(3.minutes, MyWorker, 'foo', 1, :bat => 'bar') # def enqueue_in(interval, klass, *args) klass.perform_in(interval, *args) end end private def raw_push(payloads) @redis_pool.with do |conn| conn.multi do atomic_push(conn, payloads) end end true end def atomic_push(conn, payloads) if payloads.first['at'] conn.zadd('schedule'.freeze, payloads.map do |hash| at = hash.delete('at'.freeze).to_s [at, Sidekiq.dump_json(hash)] end) else q = payloads.first['queue'] now = Time.now.to_f to_push = payloads.map do |entry| entry['enqueued_at'.freeze] = now Sidekiq.dump_json(entry) end conn.sadd('queues'.freeze, q) conn.lpush("queue:#{q}", to_push) end end def process_single(worker_class, item) queue = item['queue'] middleware.invoke(worker_class, item, queue, @redis_pool) do item end end def normalize_item(item) raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class'.freeze) && item.has_key?('args'.freeze) raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array) raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'.freeze].is_a?(Class) || item['class'.freeze].is_a?(String) normalized_hash(item['class'.freeze]) .each{ |key, value| item[key] = value if item[key].nil? } item['class'.freeze] = item['class'.freeze].to_s item['queue'.freeze] = item['queue'.freeze].to_s item['jid'.freeze] ||= SecureRandom.hex(12) item['created_at'.freeze] ||= Time.now.to_f item end def normalized_hash(item_class) if item_class.is_a?(Class) raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options'.freeze) item_class.get_sidekiq_options else Sidekiq.default_worker_options end end end end sidekiq-4.0.1/lib/sidekiq/manager.rb0000644000175600017570000000651412631157272016360 0ustar pravipravi# encoding: utf-8 require 'sidekiq/util' require 'sidekiq/processor' require 'sidekiq/fetch' require 'thread' module Sidekiq ## # The Manager is the central coordination point in Sidekiq, controlling # the lifecycle of the Processors and feeding them jobs as necessary. # # Tasks: # # 1. start: Spin up Processors. # 3. processor_died: Handle job failure, throw away Processor, create new one. # 4. quiet: shutdown idle Processors. # 5. stop: hard stop the Processors by deadline. # # Note that only the last task requires its own Thread since it has to monitor # the shutdown process. The other tasks are performed by other threads. # class Manager include Util attr_reader :workers attr_reader :options def initialize(options={}) logger.debug { options.inspect } @options = options @count = options[:concurrency] || 25 raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1 @done = false @workers = Set.new @count.times do @workers << Processor.new(self) end @plock = Mutex.new end def start @workers.each do |x| x.start end end def quiet return if @done @done = true logger.info { "Terminating quiet workers" } @workers.each { |x| x.terminate } fire_event(:quiet, true) end def stop(deadline) quiet fire_event(:shutdown, true) # some of the shutdown events can be async, # we don't have any way to know when they're done but # give them a little time to take effect sleep 0.5 return if @workers.empty? logger.info { "Pausing to allow workers to finish..." } remaining = deadline - Time.now while remaining > 0.5 return if @workers.empty? sleep 0.5 remaining = deadline - Time.now end return if @workers.empty? hard_shutdown end def processor_stopped(processor) @plock.synchronize do @workers.delete(processor) end end def processor_died(processor, reason) @plock.synchronize do @workers.delete(processor) unless @done p = Processor.new(self) @workers << p p.start end end end def stopped? @done end private def hard_shutdown # We've reached the timeout and we still have busy workers. # They must die but their jobs shall live on. cleanup = nil @plock.synchronize do cleanup = @workers.dup end if cleanup.size > 0 jobs = cleanup.map {|p| p.job }.compact logger.warn { "Terminating #{cleanup.size} busy worker threads" } logger.warn { "Work still in progress #{jobs.inspect}" } # Re-enqueue unfinished jobs # NOTE: You may notice that we may push a job back to redis before # the worker thread is terminated. This is ok because Sidekiq's # contract says that jobs are run AT LEAST once. Process termination # is delayed until we're certain the jobs are back in Redis because # it is worse to lose a job than to run it twice. strategy = (@options[:fetch] || Sidekiq::BasicFetch) strategy.bulk_requeue(jobs, @options) end cleanup.each do |processor| processor.kill end end end end sidekiq-4.0.1/lib/sidekiq/core_ext.rb0000644000175600017570000000463112631157272016554 0ustar pravipravibegin require 'active_support/core_ext/class/attribute' rescue LoadError # A dumbed down version of ActiveSupport's # Class#class_attribute helper. class Class def class_attribute(*attrs) instance_writer = true attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.#{name}() nil end def self.#{name}?() !!#{name} end def self.#{name}=(val) singleton_class.class_eval do define_method(:#{name}) { val } end if singleton_class? class_eval do def #{name} defined?(@#{name}) ? @#{name} : singleton_class.#{name} end end end val end def #{name} defined?(@#{name}) ? @#{name} : self.class.#{name} end def #{name}? !!#{name} end RUBY attr_writer name if instance_writer end end private def singleton_class? ancestors.first != self end end end begin require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/deep_merge' rescue LoadError class Hash def stringify_keys keys.each do |key| self[key.to_s] = delete(key) end self end if !{}.respond_to?(:stringify_keys) def symbolize_keys keys.each do |key| self[(key.to_sym rescue key) || key] = delete(key) end self end if !{}.respond_to?(:symbolize_keys) def deep_merge(other_hash, &block) dup.deep_merge!(other_hash, &block) end if !{}.respond_to?(:deep_merge) def deep_merge!(other_hash, &block) other_hash.each_pair do |k,v| tv = self[k] if tv.is_a?(Hash) && v.is_a?(Hash) self[k] = tv.deep_merge(v, &block) else self[k] = block && tv ? block.call(k, tv, v) : v end end self end if !{}.respond_to?(:deep_merge!) end end begin require 'active_support/core_ext/string/inflections' rescue LoadError class String def constantize names = self.split('::') names.shift if names.empty? || names.first.empty? constant = Object names.each do |name| constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) end constant end end if !"".respond_to?(:constantize) end sidekiq-4.0.1/lib/sidekiq/exception_handler.rb0000644000175600017570000000150012631157272020427 0ustar pravipravirequire 'sidekiq' module Sidekiq module ExceptionHandler class Logger def call(ex, ctxHash) Sidekiq.logger.warn(ctxHash) if !ctxHash.empty? Sidekiq.logger.warn "#{ex.class.name}: #{ex.message}" Sidekiq.logger.warn ex.backtrace.join("\n") unless ex.backtrace.nil? end # Set up default handler which just logs the error Sidekiq.error_handlers << Sidekiq::ExceptionHandler::Logger.new end def handle_exception(ex, ctxHash={}) Sidekiq.error_handlers.each do |handler| begin handler.call(ex, ctxHash) rescue => ex Sidekiq.logger.error "!!! ERROR HANDLER THREW AN ERROR !!!" Sidekiq.logger.error ex Sidekiq.logger.error ex.backtrace.join("\n") unless ex.backtrace.nil? end end end end end sidekiq-4.0.1/lib/sidekiq/scheduled.rb0000644000175600017570000001141512631157272016702 0ustar pravipravirequire 'sidekiq' require 'sidekiq/util' require 'sidekiq/api' module Sidekiq module Scheduled SETS = %w(retry schedule) class Enq def enqueue_jobs(now=Time.now.to_f.to_s, sorted_sets=SETS) # A job's "score" in Redis is the time at which it should be processed. # Just check Redis for the set of jobs with a timestamp before now. Sidekiq.redis do |conn| sorted_sets.each do |sorted_set| # Get the next item in the queue if it's score (time to execute) is <= now. # We need to go through the list one at a time to reduce the risk of something # going wrong between the time jobs are popped from the scheduled queue and when # they are pushed onto a work queue and losing the jobs. while job = conn.zrangebyscore(sorted_set, '-inf'.freeze, now, :limit => [0, 1]).first do # Pop item off the queue and add it to the work queue. If the job can't be popped from # the queue, it's because another process already popped it so we can move on to the # next one. if conn.zrem(sorted_set, job) Sidekiq::Client.push(Sidekiq.load_json(job)) Sidekiq::Logging.logger.debug { "enqueued #{sorted_set}: #{job}" } end end end end end end ## # The Poller checks Redis every N seconds for jobs in the retry or scheduled # set have passed their timestamp and should be enqueued. If so, it # just pops the job back onto its original queue so the # workers can pick it up like any other job. class Poller include Util INITIAL_WAIT = 10 def initialize @enq = (Sidekiq.options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new @sleeper = ConnectionPool::TimedStack.new @done = false end # Shut down this instance, will pause until the thread is dead. def terminate @done = true if @thread t = @thread @thread = nil @sleeper << 0 t.value end end def start @thread ||= safe_thread("scheduler") do initial_wait while !@done enqueue wait end Sidekiq.logger.info("Scheduler exiting...") end end def enqueue begin @enq.enqueue_jobs rescue => ex # Most likely a problem with redis networking. # Punt and try again at the next interval logger.error ex.message logger.error ex.backtrace.first end end private def wait @sleeper.pop(random_poll_interval) rescue Timeout::Error end # Calculates a random interval that is ±50% the desired average. def random_poll_interval poll_interval_average * rand + poll_interval_average.to_f / 2 end # We do our best to tune the poll interval to the size of the active Sidekiq # cluster. If you have 30 processes and poll every 15 seconds, that means one # Sidekiq is checking Redis every 0.5 seconds - way too often for most people # and really bad if the retry or scheduled sets are large. # # Instead try to avoid polling more than once every 15 seconds. If you have # 30 Sidekiq processes, we'll poll every 30 * 15 or 450 seconds. # To keep things statistically random, we'll sleep a random amount between # 225 and 675 seconds for each poll or 450 seconds on average. Otherwise restarting # all your Sidekiq processes at the same time will lead to them all polling at # the same time: the thundering herd problem. # # We only do this if poll_interval_average is unset (the default). def poll_interval_average Sidekiq.options[:poll_interval_average] ||= scaled_poll_interval end # Calculates an average poll interval based on the number of known Sidekiq processes. # This minimizes a single point of failure by dispersing check-ins but without taxing # Redis if you run many Sidekiq processes. def scaled_poll_interval pcount = Sidekiq::ProcessSet.new.size pcount = 1 if pcount == 0 pcount * Sidekiq.options[:average_scheduled_poll_interval] end def initial_wait # Have all processes sleep between 5-15 seconds. 10 seconds # to give time for the heartbeat to register (if the poll interval is going to be calculated by the number # of workers), and 5 random seconds to ensure they don't all hit Redis at the same time. total = 0 total += INITIAL_WAIT unless Sidekiq.options[:poll_interval_average] total += (5 * rand) @sleeper.pop(total) rescue Timeout::Error end end end end sidekiq-4.0.1/lib/sidekiq/web_helpers.rb0000644000175600017570000001467412631157272017253 0ustar pravipravirequire 'uri' module Sidekiq # This is not a public API module WebHelpers def strings(lang) @@strings ||= {} @@strings[lang] ||= begin # Allow sidekiq-web extensions to add locale paths # so extensions can be localized settings.locales.each_with_object({}) do |path, global| find_locale_files(lang).each do |file| strs = YAML.load(File.open(file)) global.deep_merge!(strs[lang]) end end end end def locale_files @@locale_files = settings.locales.flat_map do |path| Dir["#{path}/*.yml"] end end def find_locale_files(lang) locale_files.select { |file| file =~ /\/#{lang}\.yml$/ } end # This is a hook for a Sidekiq Pro feature. Please don't touch. def filtering(*) end # This view helper provide ability display you html code in # to head of page. Example: # # <% add_to_head do %> # # # <% end %> # def add_to_head(&block) @head_html ||= [] @head_html << block if block_given? end def display_custom_head return unless defined?(@head_html) @head_html.map { |block| capture(&block) }.join end # Simple capture method for erb templates. The origin was # capture method from sinatra-contrib library. def capture(&block) block.call eval('', block.binding) end # Given a browser request Accept-Language header like # "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function # will return "fr" since that's the first code with a matching # locale in web/locales def locale @locale ||= begin locale = 'en'.freeze languages = request.env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze languages.downcase.split(','.freeze).each do |lang| next if lang == '*'.freeze lang = lang.split(';'.freeze)[0] break locale = lang if find_locale_files(lang).any? end locale end end def get_locale strings(locale) end def t(msg, options={}) string = get_locale[msg] || msg if options.empty? string else string % options end end def workers @workers ||= Sidekiq::Workers.new end def processes @processes ||= Sidekiq::ProcessSet.new end def stats @stats ||= Sidekiq::Stats.new end def retries_with_score(score) Sidekiq.redis do |conn| conn.zrangebyscore('retry', score, score) end.map { |msg| Sidekiq.load_json(msg) } end def location Sidekiq.redis { |conn| conn.client.location } end def redis_connection Sidekiq.redis { |conn| conn.client.id } end def namespace @@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil } end def redis_info Sidekiq.redis do |conn| # admin commands can't go through redis-namespace starting # in redis-namespace 2.0 if conn.respond_to?(:namespace) conn.redis.info else conn.info end end end def root_path "#{env['SCRIPT_NAME']}/" end def current_path @current_path ||= request.path_info.gsub(/^\//,'') end def current_status workers.size == 0 ? 'idle' : 'active' end def relative_time(time) %{} end def job_params(job, score) "#{score}-#{job['jid']}" end def parse_params(params) score, jid = params.split("-") [score.to_f, jid] end SAFE_QPARAMS = %w(page poll) # Merge options with current params, filter safe params, and stringify to query string def qparams(options) options = options.stringify_keys params.merge(options).map do |key, value| SAFE_QPARAMS.include?(key) ? "#{key}=#{value}" : next end.compact.join("&") end def truncate(text, truncate_after_chars = 2000) truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text end def display_args(args, truncate_after_chars = 2000) args.map do |arg| h(truncate(to_display(arg))) end.join(", ") end def csrf_tag "" end def to_display(arg) begin arg.inspect rescue begin arg.to_s rescue => ex "Cannot display argument: [#{ex.class.name}] #{ex.message}" end end end RETRY_JOB_KEYS = Set.new(%w( queue class args retry_count retried_at failed_at jid error_message error_class backtrace error_backtrace enqueued_at retry wrapped )) def retry_extra_items(retry_job) @retry_extra_items ||= {}.tap do |extra| retry_job.item.each do |key, value| extra[key] = value unless RETRY_JOB_KEYS.include?(key) end end end def number_with_delimiter(number) begin Float(number) rescue ArgumentError, TypeError return number end options = {delimiter: ',', separator: '.'} parts = number.to_s.to_str.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") parts.join(options[:separator]) end def h(text) ::Rack::Utils.escape_html(text) rescue ArgumentError => e raise unless e.message.eql?('invalid byte sequence in UTF-8') text.encode!('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode!('UTF-8', 'UTF-16') retry end # Any paginated list that performs an action needs to redirect # back to the proper page after performing that action. def redirect_with_query(url) r = request.referer if r && r =~ /\?/ ref = URI(r) redirect("#{url}?#{ref.query}") else redirect url end end def environment_title_prefix environment = Sidekiq.options[:environment] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development' "[#{environment.upcase}] " unless environment == "production" end def product_version "Sidekiq v#{Sidekiq::VERSION}" end def redis_connection_and_namespace @redis_connection_and_namespace ||= begin namespace_suffix = namespace == nil ? '' : "##{namespace}" "#{redis_connection}#{namespace_suffix}" end end end end sidekiq-4.0.1/lib/sidekiq/worker.rb0000644000175600017570000000606312631157272016256 0ustar pravipravirequire 'sidekiq/client' require 'sidekiq/core_ext' module Sidekiq ## # Include this module in your worker class and you can easily create # asynchronous jobs: # # class HardWorker # include Sidekiq::Worker # # def perform(*args) # # do some work # end # end # # Then in your Rails app, you can do this: # # HardWorker.perform_async(1, 2, 3) # # Note that perform_async is a class method, perform is an instance method. module Worker attr_accessor :jid def self.included(base) raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}" if base.ancestors.any? {|c| c.name == 'ActiveJob::Base' } base.extend(ClassMethods) base.class_attribute :sidekiq_options_hash base.class_attribute :sidekiq_retry_in_block base.class_attribute :sidekiq_retries_exhausted_block end def logger Sidekiq.logger end module ClassMethods def delay(*args) raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async" end def delay_for(*args) raise ArgumentError, "Do not call .delay_for on a Sidekiq::Worker class, call .perform_in" end def delay_until(*args) raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at" end def perform_async(*args) client_push('class' => self, 'args' => args) end def perform_in(interval, *args) int = interval.to_f now = Time.now ts = (int < 1_000_000_000 ? (now + interval).to_f : int) item = { 'class' => self, 'args' => args, 'at' => ts } # Optimization to enqueue something now that is scheduled to go out now or in the past item.delete('at'.freeze) if ts <= now.to_f client_push(item) end alias_method :perform_at, :perform_in ## # Allows customization for this type of Worker. # Legal options: # # :queue - use a named queue for this Worker, default 'default' # :retry - enable the RetryJobs middleware for this Worker, default *true* # :backtrace - whether to save any error backtrace in the retry payload to display in web UI, # can be true, false or an integer number of lines to save, default *false* # :pool - use the given Redis connection pool to push this type of job to a given shard. def sidekiq_options(opts={}) self.sidekiq_options_hash = get_sidekiq_options.merge(opts.stringify_keys) end def sidekiq_retry_in(&block) self.sidekiq_retry_in_block = block end def sidekiq_retries_exhausted(&block) self.sidekiq_retries_exhausted_block = block end def get_sidekiq_options # :nodoc: self.sidekiq_options_hash ||= Sidekiq.default_worker_options end def client_push(item) # :nodoc: pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool Sidekiq::Client.new(pool).push(item.stringify_keys) end end end end sidekiq-4.0.1/lib/sidekiq/web.rb0000644000175600017570000001704012631157272015517 0ustar pravipravirequire 'erb' require 'yaml' require 'sinatra/base' require 'sidekiq' require 'sidekiq/api' require 'sidekiq/paginator' require 'sidekiq/web_helpers' module Sidekiq class Web < Sinatra::Base include Sidekiq::Paginator enable :sessions use Rack::Protection, :use => :authenticity_token unless ENV['RACK_ENV'] == 'test' set :root, File.expand_path(File.dirname(__FILE__) + "/../../web") set :public_folder, proc { "#{root}/assets" } set :views, proc { "#{root}/views" } set :locales, ["#{root}/locales"] helpers WebHelpers DEFAULT_TABS = { "Dashboard" => '', "Busy" => 'busy', "Queues" => 'queues', "Retries" => 'retries', "Scheduled" => 'scheduled', "Dead" => 'morgue', } class << self def default_tabs DEFAULT_TABS end def custom_tabs @custom_tabs ||= {} end alias_method :tabs, :custom_tabs attr_accessor :app_url end get "/busy" do erb :busy end post "/busy" do if params['identity'] p = Sidekiq::Process.new('identity' => params['identity']) p.quiet! if params[:quiet] p.stop! if params[:stop] else processes.each do |pro| pro.quiet! if params[:quiet] pro.stop! if params[:stop] end end redirect "#{root_path}busy" end get "/queues" do @queues = Sidekiq::Queue.all erb :queues end get "/queues/:name" do halt 404 unless params[:name] @count = (params[:count] || 25).to_i @name = params[:name] @queue = Sidekiq::Queue.new(@name) (@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count) @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) } erb :queue end post "/queues/:name" do Sidekiq::Queue.new(params[:name]).clear redirect "#{root_path}queues" end post "/queues/:name/delete" do Sidekiq::Job.new(params[:key_val], params[:name]).delete redirect_with_query("#{root_path}queues/#{params[:name]}") end get '/morgue' do @count = (params[:count] || 25).to_i (@current_page, @total_size, @dead) = page("dead", params[:page], @count, reverse: true) @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } erb :morgue end get "/morgue/:key" do halt 404 unless params['key'] @dead = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first redirect "#{root_path}morgue" if @dead.nil? erb :dead end post '/morgue' do redirect request.path unless params['key'] params['key'].each do |key| job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first retry_or_delete_or_kill job, params if job end redirect_with_query("#{root_path}morgue") end post "/morgue/all/delete" do Sidekiq::DeadSet.new.clear redirect "#{root_path}morgue" end post "/morgue/all/retry" do Sidekiq::DeadSet.new.retry_all redirect "#{root_path}morgue" end post "/morgue/:key" do halt 404 unless params['key'] job = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first retry_or_delete_or_kill job, params if job redirect_with_query("#{root_path}morgue") end get '/retries' do @count = (params[:count] || 25).to_i (@current_page, @total_size, @retries) = page("retry", params[:page], @count) @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } erb :retries end get "/retries/:key" do @retry = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first redirect "#{root_path}retries" if @retry.nil? erb :retry end post '/retries' do redirect request.path unless params['key'] params['key'].each do |key| job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first retry_or_delete_or_kill job, params if job end redirect_with_query("#{root_path}retries") end post "/retries/all/delete" do Sidekiq::RetrySet.new.clear redirect "#{root_path}retries" end post "/retries/all/retry" do Sidekiq::RetrySet.new.retry_all redirect "#{root_path}retries" end post "/retries/:key" do job = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first retry_or_delete_or_kill job, params if job redirect_with_query("#{root_path}retries") end get '/scheduled' do @count = (params[:count] || 25).to_i (@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count) @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } erb :scheduled end get "/scheduled/:key" do @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first redirect "#{root_path}scheduled" if @job.nil? erb :scheduled_job_info end post '/scheduled' do redirect request.path unless params['key'] params['key'].each do |key| job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first delete_or_add_queue job, params if job end redirect_with_query("#{root_path}scheduled") end post "/scheduled/:key" do halt 404 unless params['key'] job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first delete_or_add_queue job, params if job redirect_with_query("#{root_path}scheduled") end get '/' do @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k } stats_history = Sidekiq::Stats::History.new((params[:days] || 30).to_i) @processed_history = stats_history.processed @failed_history = stats_history.failed erb :dashboard end REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human) get '/dashboard/stats' do redirect "#{root_path}stats" end get '/stats' do sidekiq_stats = Sidekiq::Stats.new redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k } content_type :json Sidekiq.dump_json( sidekiq: { processed: sidekiq_stats.processed, failed: sidekiq_stats.failed, busy: sidekiq_stats.workers_size, processes: sidekiq_stats.processes_size, enqueued: sidekiq_stats.enqueued, scheduled: sidekiq_stats.scheduled_size, retries: sidekiq_stats.retry_size, dead: sidekiq_stats.dead_size, default_latency: sidekiq_stats.default_queue_latency }, redis: redis_stats ) end get '/stats/queues' do queue_stats = Sidekiq::Stats::Queues.new content_type :json Sidekiq.dump_json( queue_stats.lengths ) end private def retry_or_delete_or_kill job, params if params['retry'] job.retry elsif params['delete'] job.delete elsif params['kill'] job.kill end end def delete_or_add_queue job, params if params['delete'] job.delete elsif params['add_to_queue'] job.add_to_queue end end end end if defined?(::ActionDispatch::Request::Session) && !::ActionDispatch::Request::Session.respond_to?(:each) # mperham/sidekiq#2460 # Rack apps can't reuse the Rails session store without # this monkeypatch class ActionDispatch::Request::Session def each(&block) hash = self.to_hash hash.each(&block) end end end sidekiq-4.0.1/lib/sidekiq/testing/0000755000175600017570000000000012631157272016070 5ustar pravipravisidekiq-4.0.1/lib/sidekiq/testing/inline.rb0000644000175600017570000000114012631157272017667 0ustar pravipravirequire 'sidekiq/testing' ## # The Sidekiq inline infrastructure overrides perform_async so that it # actually calls perform instead. This allows workers to be run inline in a # testing environment. # # This is similar to `Resque.inline = true` functionality. # # Example: # # require 'sidekiq/testing/inline' # # $external_variable = 0 # # class ExternalWorker # include Sidekiq::Worker # # def perform # $external_variable = 1 # end # end # # assert_equal 0, $external_variable # ExternalWorker.perform_async # assert_equal 1, $external_variable # Sidekiq::Testing.inline! sidekiq-4.0.1/lib/sidekiq/logging.rb0000644000175600017570000000575612631157272016403 0ustar pravipravirequire 'time' require 'logger' module Sidekiq module Logging class Pretty < Logger::Formatter SPACE = " " # Provide a call() method that returns the formatted message. def call(severity, time, program_name, message) "#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n" end def context c = Thread.current[:sidekiq_context] " #{c.join(SPACE)}" if c && c.any? end end class WithoutTimestamp < Pretty def call(severity, time, program_name, message) "#{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n" end end def self.with_context(msg) Thread.current[:sidekiq_context] ||= [] Thread.current[:sidekiq_context] << msg yield ensure Thread.current[:sidekiq_context].pop end def self.initialize_logger(log_target = STDOUT) oldlogger = defined?(@logger) ? @logger : nil @logger = Logger.new(log_target) @logger.level = Logger::INFO @logger.formatter = ENV['DYNO'] ? WithoutTimestamp.new : Pretty.new oldlogger.close if oldlogger && !$TESTING # don't want to close testing's STDOUT logging @logger end def self.logger defined?(@logger) ? @logger : initialize_logger end def self.logger=(log) @logger = (log ? log : Logger.new('/dev/null')) end # This reopens ALL logfiles in the process that have been rotated # using logrotate(8) (without copytruncate) or similar tools. # A +File+ object is considered for reopening if it is: # 1) opened with the O_APPEND and O_WRONLY flags # 2) the current open file handle does not match its original open path # 3) unbuffered (as far as userspace buffering goes, not O_SYNC) # Returns the number of files reopened def self.reopen_logs to_reopen = [] append_flags = File::WRONLY | File::APPEND ObjectSpace.each_object(File) do |fp| begin if !fp.closed? && fp.stat.file? && fp.sync && (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags to_reopen << fp end rescue IOError, Errno::EBADF end end nr = 0 to_reopen.each do |fp| orig_st = begin fp.stat rescue IOError, Errno::EBADF next end begin b = File.stat(fp.path) next if orig_st.ino == b.ino && orig_st.dev == b.dev rescue Errno::ENOENT end begin File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) } fp.sync = true nr += 1 rescue IOError, Errno::EBADF # not much we can do... end end nr rescue RuntimeError => ex # RuntimeError: ObjectSpace is disabled; each_object will only work with Class, pass -X+O to enable puts "Unable to reopen logs: #{ex.message}" end def logger Sidekiq::Logging.logger end end end sidekiq-4.0.1/lib/sidekiq/util.rb0000644000175600017570000000322212631157272015714 0ustar pravipravirequire 'socket' require 'securerandom' require 'sidekiq/exception_handler' require 'sidekiq/core_ext' module Sidekiq ## # This module is part of Sidekiq core and not intended for extensions. # module Util include ExceptionHandler EXPIRY = 60 * 60 * 24 def watchdog(last_words) yield rescue Exception => ex handle_exception(ex, { context: last_words }) raise ex end def safe_thread(name, &block) Thread.new do watchdog(name, &block) end end def logger Sidekiq.logger end def redis(&block) Sidekiq.redis(&block) end def hostname ENV['DYNO'] || Socket.gethostname end def process_nonce @@process_nonce ||= SecureRandom.hex(6) end def identity @@identity ||= "#{hostname}:#{$$}:#{process_nonce}" end def fire_event(event, reverse=false) arr = Sidekiq.options[:lifecycle_events][event] arr.reverse! if reverse arr.each do |block| begin block.call rescue => ex handle_exception(ex, { event: event }) end end arr.clear end def want_a_hertz_donut? # what's a hertz donut? # punch! Hurts, don't it? info = Sidekiq.redis {|c| c.info } if info['connected_clients'].to_i > 1000 && info['hz'].to_i >= 10 Sidekiq.logger.warn { "Your Redis `hz` setting is too high at #{info['hz']}. See mperham/sidekiq#2431. Set it to 3 in #{info[:config_file]}" } true else Sidekiq.logger.debug { "Redis hz: #{info['hz']}. Client count: #{info['connected_clients']}" } false end end end end sidekiq-4.0.1/lib/sidekiq/paginator.rb0000644000175600017570000000210112631157272016716 0ustar pravipravimodule Sidekiq module Paginator def page(key, pageidx=1, page_size=25, opts=nil) current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i pageidx = current_page - 1 total_size = 0 items = [] starting = pageidx * page_size ending = starting + page_size - 1 Sidekiq.redis do |conn| type = conn.type(key) case type when 'zset' rev = opts && opts[:reverse] total_size, items = conn.multi do conn.zcard(key) if rev conn.zrevrange(key, starting, ending, :with_scores => true) else conn.zrange(key, starting, ending, :with_scores => true) end end [current_page, total_size, items] when 'list' total_size, items = conn.multi do conn.llen(key) conn.lrange(key, starting, ending) end [current_page, total_size, items] when 'none' [1, 0, []] else raise "can't page a #{type}" end end end end end sidekiq-4.0.1/lib/sidekiq/middleware/0000755000175600017570000000000012631157272016530 5ustar pravipravisidekiq-4.0.1/lib/sidekiq/middleware/server/0000755000175600017570000000000012631157272020036 5ustar pravipravisidekiq-4.0.1/lib/sidekiq/middleware/server/active_record.rb0000644000175600017570000000034612631157272023177 0ustar pravipravimodule Sidekiq module Middleware module Server class ActiveRecord def call(*args) yield ensure ::ActiveRecord::Base.clear_active_connections! end end end end end sidekiq-4.0.1/lib/sidekiq/middleware/server/retry_jobs.rb0000644000175600017570000001630512631157272022552 0ustar pravipravirequire 'sidekiq/scheduled' require 'sidekiq/api' module Sidekiq module Middleware module Server ## # Automatically retry jobs that fail in Sidekiq. # Sidekiq's retry support assumes a typical development lifecycle: # # 0. push some code changes with a bug in it # 1. bug causes job processing to fail, sidekiq's middleware captures # the job and pushes it onto a retry queue # 2. sidekiq retries jobs in the retry queue multiple times with # an exponential delay, the job continues to fail # 3. after a few days, a developer deploys a fix. the job is # reprocessed successfully. # 4. once retries are exhausted, sidekiq will give up and move the # job to the Dead Job Queue (aka morgue) where it must be dealt with # manually in the Web UI. # 5. After 6 months on the DJQ, Sidekiq will discard the job. # # A job looks like: # # { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'], 'retry' => true } # # The 'retry' option also accepts a number (in place of 'true'): # # { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'], 'retry' => 5 } # # The job will be retried this number of times before giving up. (If simply # 'true', Sidekiq retries 25 times) # # We'll add a bit more data to the job to support retries: # # * 'queue' - the queue to use # * 'retry_count' - number of times we've retried so far. # * 'error_message' - the message from the exception # * 'error_class' - the exception class # * 'failed_at' - the first time it failed # * 'retried_at' - the last time it was retried # * 'backtrace' - the number of lines of error backtrace to store # # We don't store the backtrace by default as that can add a lot of overhead # to the job and everyone is using an error service, right? # # The default number of retry attempts is 25 which works out to about 3 weeks # of retries. You can pass a value for the max number of retry attempts when # adding the middleware using the options hash: # # Sidekiq.configure_server do |config| # config.server_middleware do |chain| # chain.add Sidekiq::Middleware::Server::RetryJobs, :max_retries => 7 # end # end # # or limit the number of retries for a particular worker with: # # class MyWorker # include Sidekiq::Worker # sidekiq_options :retry => 10 # end # class RetryJobs include Sidekiq::Util DEFAULT_MAX_RETRY_ATTEMPTS = 25 def initialize(options = {}) @max_retries = options.fetch(:max_retries, DEFAULT_MAX_RETRY_ATTEMPTS) end def call(worker, msg, queue) yield rescue Sidekiq::Shutdown # ignore, will be pushed back onto queue during hard_shutdown raise rescue Exception => e # ignore, will be pushed back onto queue during hard_shutdown raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e) raise e unless msg['retry'] attempt_retry(worker, msg, queue, e) end private def attempt_retry(worker, msg, queue, exception) max_retry_attempts = retry_attempts_from(msg['retry'], @max_retries) msg['queue'] = if msg['retry_queue'] msg['retry_queue'] else queue end # App code can stuff all sorts of crazy binary data into the error message # that won't convert to JSON. m = exception.message[0..10_000] if m.respond_to?(:scrub!) m.force_encoding("utf-8") m.scrub! end msg['error_message'] = m msg['error_class'] = exception.class.name count = if msg['retry_count'] msg['retried_at'] = Time.now.to_f msg['retry_count'] += 1 else msg['failed_at'] = Time.now.to_f msg['retry_count'] = 0 end if msg['backtrace'] == true msg['error_backtrace'] = exception.backtrace elsif !msg['backtrace'] # do nothing elsif msg['backtrace'].to_i != 0 msg['error_backtrace'] = exception.backtrace[0...msg['backtrace'].to_i] end if count < max_retry_attempts delay = delay_for(worker, count, exception) logger.debug { "Failure! Retry #{count} in #{delay} seconds" } retry_at = Time.now.to_f + delay payload = Sidekiq.dump_json(msg) Sidekiq.redis do |conn| conn.zadd('retry', retry_at.to_s, payload) end else # Goodbye dear message, you (re)tried your best I'm sure. retries_exhausted(worker, msg) end raise exception end def retries_exhausted(worker, msg) logger.debug { "Dropping message after hitting the retry maximum: #{msg}" } begin if worker.sidekiq_retries_exhausted_block? worker.sidekiq_retries_exhausted_block.call(msg) end rescue => e handle_exception(e, { context: "Error calling retries_exhausted for #{worker.class}", job: msg }) end send_to_morgue(msg) unless msg['dead'] == false end def send_to_morgue(msg) Sidekiq.logger.info { "Adding dead #{msg['class']} job #{msg['jid']}" } payload = Sidekiq.dump_json(msg) now = Time.now.to_f Sidekiq.redis do |conn| conn.multi do conn.zadd('dead', now, payload) conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout) conn.zremrangebyrank('dead', 0, -DeadSet.max_jobs) end end end def retry_attempts_from(msg_retry, default) if msg_retry.is_a?(Fixnum) msg_retry else default end end def delay_for(worker, count, exception) worker.sidekiq_retry_in_block? && retry_in(worker, count, exception) || seconds_to_delay(count) end # delayed_job uses the same basic formula def seconds_to_delay(count) (count ** 4) + 15 + (rand(30)*(count+1)) end def retry_in(worker, count, exception) begin worker.sidekiq_retry_in_block.call(count, exception).to_i rescue Exception => e handle_exception(e, { context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{worker.class.name}, falling back to default" }) nil end end def exception_caused_by_shutdown?(e, checked_causes = []) # In Ruby 2.1.0 only, check if exception is a result of shutdown. return false unless defined?(e.cause) # Handle circular causes checked_causes << e.object_id return false if checked_causes.include?(e.cause.object_id) e.cause.instance_of?(Sidekiq::Shutdown) || exception_caused_by_shutdown?(e.cause, checked_causes) end end end end end sidekiq-4.0.1/lib/sidekiq/middleware/server/logging.rb0000644000175600017570000000177612631157272022024 0ustar pravipravimodule Sidekiq module Middleware module Server class Logging def call(worker, item, queue) Sidekiq::Logging.with_context(log_context(worker, item)) do begin start = Time.now logger.info { "start" } yield logger.info { "done: #{elapsed(start)} sec" } rescue Exception logger.info { "fail: #{elapsed(start)} sec" } raise end end end private # If we're using a wrapper class, like ActiveJob, use the "wrapped" # attribute to expose the underlying thing. def log_context(worker, item) klass = item['wrapped'.freeze] || worker.class.to_s "#{klass} JID-#{item['jid'.freeze]}#{" BID-#{item['bid'.freeze]}" if item['bid'.freeze]}" end def elapsed(start) (Time.now - start).round(3) end def logger Sidekiq.logger end end end end end sidekiq-4.0.1/lib/sidekiq/middleware/chain.rb0000644000175600017570000000712612631157272020145 0ustar pravipravimodule Sidekiq # Middleware is code configured to run before/after # a message is processed. It is patterned after Rack # middleware. Middleware exists for the client side # (pushing jobs onto the queue) as well as the server # side (when jobs are actually processed). # # To add middleware for the client: # # Sidekiq.configure_client do |config| # config.client_middleware do |chain| # chain.add MyClientHook # end # end # # To modify middleware for the server, just call # with another block: # # Sidekiq.configure_server do |config| # config.server_middleware do |chain| # chain.add MyServerHook # chain.remove ActiveRecord # end # end # # To insert immediately preceding another entry: # # Sidekiq.configure_client do |config| # config.client_middleware do |chain| # chain.insert_before ActiveRecord, MyClientHook # end # end # # To insert immediately after another entry: # # Sidekiq.configure_client do |config| # config.client_middleware do |chain| # chain.insert_after ActiveRecord, MyClientHook # end # end # # This is an example of a minimal server middleware: # # class MyServerHook # def call(worker_instance, msg, queue) # puts "Before work" # yield # puts "After work" # end # end # # This is an example of a minimal client middleware, note # the method must return the result or the job will not push # to Redis: # # class MyClientHook # def call(worker_class, msg, queue, redis_pool) # puts "Before push" # result = yield # puts "After push" # result # end # end # module Middleware class Chain include Enumerable attr_reader :entries def initialize_copy(copy) copy.instance_variable_set(:@entries, entries.dup) end def each(&block) entries.each(&block) end def initialize @entries = [] yield self if block_given? end def remove(klass) entries.delete_if { |entry| entry.klass == klass } end def add(klass, *args) remove(klass) if exists?(klass) entries << Entry.new(klass, *args) end def prepend(klass, *args) remove(klass) if exists?(klass) entries.insert(0, Entry.new(klass, *args)) end def insert_before(oldklass, newklass, *args) i = entries.index { |entry| entry.klass == newklass } new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i) i = entries.index { |entry| entry.klass == oldklass } || 0 entries.insert(i, new_entry) end def insert_after(oldklass, newklass, *args) i = entries.index { |entry| entry.klass == newklass } new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i) i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1 entries.insert(i+1, new_entry) end def exists?(klass) any? { |entry| entry.klass == klass } end def retrieve map(&:make_new) end def clear entries.clear end def invoke(*args) chain = retrieve.dup traverse_chain = lambda do if chain.empty? yield else chain.shift.call(*args, &traverse_chain) end end traverse_chain.call end end class Entry attr_reader :klass def initialize(klass, *args) @klass = klass @args = args end def make_new @klass.new(*@args) end end end end sidekiq-4.0.1/lib/sidekiq/middleware/i18n.rb0000644000175600017570000000201112631157272017626 0ustar pravipravi# # Simple middleware to save the current locale and restore it when the job executes. # Use it by requiring it in your initializer: # # require 'sidekiq/middleware/i18n' # module Sidekiq::Middleware::I18n # Get the current locale and store it in the message # to be sent to Sidekiq. class Client def call(worker_class, msg, queue, redis_pool) msg['locale'] ||= I18n.locale yield end end # Pull the msg locale out and set the current thread to use it. class Server def call(worker, msg, queue) I18n.locale = msg['locale'] || I18n.default_locale yield ensure I18n.locale = I18n.default_locale end end end Sidekiq.configure_client do |config| config.client_middleware do |chain| chain.add Sidekiq::Middleware::I18n::Client end end Sidekiq.configure_server do |config| config.client_middleware do |chain| chain.add Sidekiq::Middleware::I18n::Client end config.server_middleware do |chain| chain.add Sidekiq::Middleware::I18n::Server end end sidekiq-4.0.1/sidekiq.gemspec0000644000175600017570000000245612631157272015221 0ustar pravipravi# -*- encoding: utf-8 -*- require File.expand_path('../lib/sidekiq/version', __FILE__) Gem::Specification.new do |gem| gem.authors = ["Mike Perham"] gem.email = ["mperham@gmail.com"] gem.summary = "Simple, efficient background processing for Ruby" gem.description = "Simple, efficient background processing for Ruby." gem.homepage = "http://sidekiq.org" gem.license = "LGPL-3.0" gem.executables = ['sidekiq', 'sidekiqctl'] gem.files = `git ls-files | grep -Ev '^(myapp|examples)'`.split("\n") gem.test_files = `git ls-files -- test/*`.split("\n") gem.name = "sidekiq" gem.require_paths = ["lib"] gem.version = Sidekiq::VERSION gem.add_dependency 'redis', '~> 3.2', '>= 3.2.1' gem.add_dependency 'connection_pool', '~> 2.2', '>= 2.2.0' gem.add_dependency 'json', '~> 1.0' gem.add_dependency 'concurrent-ruby', '~> 1.0' gem.add_development_dependency 'redis-namespace', '~> 1.5', '>= 1.5.2' gem.add_development_dependency 'sinatra', '~> 1.4', '>= 1.4.6' gem.add_development_dependency 'minitest', '~> 5.7', '>= 5.7.0' gem.add_development_dependency 'rake', '~> 10.0' gem.add_development_dependency 'rails', '~> 4', '>= 3.2.0' end sidekiq-4.0.1/Contributing.md0000644000175600017570000000210312631157272015201 0ustar pravipravi# Contributing ## Issues When opening an issue: * include the full **backtrace** with your error * include your Sidekiq initializer * list versions you are using: Ruby, Rails, Sidekiq, OS, etc. It's always better to include more info rather than less. ## Code It's always best to open an issue before investing a lot of time into a fix or new functionality. Functionality must meet my design goals and vision for the project to be accepted; I would be happy to discuss how your idea can best fit into Sidekiq. ## Legal By submitting a Pull Request, you disavow any rights or claims to any changes submitted to the Sidekiq project and assign the copyright of those changes to Contributed Systems LLC. If you cannot or do not want to reassign those rights (your employment contract for your employer may not allow this), you should not submit a PR. Open an issue and someone else can do the work. This is a legal way of saying "If you submit a PR to us, that code becomes ours". 99.9% of the time that's what you intend anyways; we hope it doesn't scare you away from contributing. sidekiq-4.0.1/3.0-Upgrade.md0000644000175600017570000000606312631157272014430 0ustar pravipravi# Upgrading to Sidekiq 3.0 Sidekiq 3.0 brings several new features but also removes old APIs and changes a few data elements in Redis. To upgrade cleanly: * Upgrade to the latest Sidekiq 2.x and run it for a few weeks. `gem 'sidekiq', '< 3'` This is only needed if you have retries pending. * 3rd party gems which use **client-side middleware** will need to update due to an API change. The Redis connection for a particular job is passed thru the middleware to handle sharding where jobs can be pushed to different redis server instances. `def call(worker_class, msg, queue, redis_pool)` Client-side middleware should use `redis_pool.with { |conn| ... }` to perform Redis operations and **not** `Sidekiq.redis`. * If you used the capistrano integration, you'll need to pull in the new [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq) gem and use it in your deploy.rb. * API changes: - `Sidekiq::Client.registered_workers` replaced by `Sidekiq::Workers.new` - `Sidekiq::Client.registered_queues` replaced by `Sidekiq::Queue.all` - `Sidekiq::Worker#retries_exhausted` replaced by `Sidekiq::Worker.sidekiq_retries_exhausted` - `Sidekiq::Workers#each` has changed significantly with a reworking of Sidekiq's internal process/thread data model. * `sidekiq/api` is no longer automatically required. If your code uses the API, you will need to require it. * Redis-to-Go is no longer transparently activated on Heroku so as to not play favorites with any particular Redis service. You need to set a config option for your app: `heroku config:set REDIS_PROVIDER=REDISTOGO_URL`. You may also use the generic `REDIS_URL`. See [Advanced Options: Setting the Location of your Redis server][1] for details. * Anyone using Airbrake, Honeybadger, Exceptional or ExceptionNotifier will need to update their error gem version to the latest to pull in Sidekiq support. Sidekiq will not provide explicit support for these services so as to not play favorites with any particular error service. * MRI 1.9 is no longer officially supported. Sidekiq's official support policy is to support the current and previous major releases of MRI and Rails. As of February 2014, that's MRI 2.1, MRI 2.0, JRuby 1.7, Rails 4.0 and Rails 3.2. I will consider PRs to fix issues found by users for other platforms/versions. ## Error Service Providers If you previously provided a middleware to capture job errors, you should instead provide a global error handler with Sidekiq 3.0. This ensures **any** error within Sidekiq will be logged appropriately, not just during job execution. ```ruby if Sidekiq::VERSION < '3' # old behavior Sidekiq.configure_server do |config| config.server_middleware do |chain| chain.add MyErrorService::Middleware end end else Sidekiq.configure_server do |config| config.error_handlers << proc {|ex,context| MyErrorService.notify(ex, context) } end end ``` Your error handler must respond to `call(exception, context_hash)`. [1]: https://github.com/mperham/sidekiq/wiki/Advanced-Options#via-env-variable sidekiq-4.0.1/COMM-LICENSE0000644000175600017570000004370012631157272013756 0ustar pravipraviEND-USER LICENSE AGREEMENT ------------------------------------------------------------------------------ IMPORTANT: THIS SOFTWARE END-USER LICENSE AGREEMENT ("EULA") IS A LEGAL AGREEMENT (“Agreement”) BETWEEN YOU (THE CUSTOMER, EITHER AS AN INDIVIDUAL OR, IF PURCHASED OR OTHERWISE ACQUIRED BY OR FOR AN ENTITY, AS AN ENTITY) AND CONTRIBUTED SYSTEMS. READ IT CAREFULLY BEFORE COMPLETING THE INSTALLATION PROCESS AND USING SIDEKIQ PRO AND RELATED SOFTWARE COMPONENTS (“SOFTWARE”). IT PROVIDES A LICENSE TO USE THE SOFTWARE AND CONTAINS WARRANTY INFORMATION AND LIABILITY DISCLAIMERS. BY INSTALLING AND USING THE SOFTWARE, YOU ARE CONFIRMING YOUR ACCEPTANCE OF THE SOFTWARE AND AGREEING TO BECOME BOUND BY THE TERMS OF THIS AGREEMENT. ------------------------------------------------------------------------------ In order to use the Software under this Agreement, you must receive a “Source URL” at the time of purchase, in accordance with the scope of use and other terms specified for each type of Software and as set forth in this Section 1 of this Agreement. 1. License Grant 1.1 General Use. This Agreement grants you a non-exclusive, non-transferable, limited license to the use rights for the Software, without the right to grant sublicenses, subject to the terms and conditions in this Agreement. The Software is licensed, not sold. 1.2 Unlimited Organization License. If you purchased an Organization License (included with the Sidekiq Pro Software), you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. You may also run an unlimited number of Workers. “Worker” means a thread within a Sidekiq server process which executes jobs. You may concurrently run the software on an unlimited number of Hosts, with each host running an unlimited number of Workers. 1.3 Limited Organization License. If you purchased an Organization License (included with the Sidekiq Enterprise Software), you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. The aggregate number of Workers run by the hosts must not exceed the maximum number of Workers authorized at the time of purchase. “Worker” means a thread within a Sidekiq server process which executes jobs. In order to run additional Workers, you must purchase an additional allowance from Contributed Systems. 1.4 Appliance License. If you purchased an Appliance License, you may distribute the Software in any applications, frameworks, or elements (collectively referred to as an “Application” or “Applications”) that you develop using the Software in accordance with this EULA, provided that such distribution does not violate the restrictions set forth in section 3 of this EULA. You must not remove, obscure or interfere with any copyright, acknowledgment, attribution, trademark, warning or disclaimer statement affixed to, incorporated in or otherwise applied in connection with the Software. You are required to ensure that the Software is not reused by or with any applications other than those with which you distribute it as permitted herein. For example, if You install the Software on a customer’s server, that customer is not permitted to use the Software independently of your Application. You must inform Contributed Systems of your knowledge of any infringing use of the Software by any of your customers. You are liable for compliance by those third parties with the terms and conditions of this EULA. You will not owe Contributed Systems any royalties for your distribution of the Software in accordance with this EULA. 1.5 Archive Copies. You are entitled to make a reasonable amount of copies of the Software for archival purposes. Each copy must reproduce all copyright and other proprietary rights notices on or in the Software Product. 1.6 Electronic Delivery. All Software and license documentation shall be delivered by electronic means unless otherwise specified on the applicable invoice or at the time of purchase. Software shall be deemed delivered when it is made available for download by you (“Delivery”). 2. Modifications. Contributed Systems shall provide you with source code so that you can create Modifications of the original software. “Modification” means: (a) any addition to or deletion from the contents of a file included in the original Software or previous Modifications created by You, or (b) any new file that contains any part of the original Software or previous Modifications. While you retain all rights to any original work authored by you as part of the Modifications, We continue to own all copyright and other intellectual property rights in the Software. 3. Restricted Uses. 3.1 You shall not (and shall not allow any third party to): (a) decompile, disassemble, or otherwise reverse engineer the Software or attempt to reconstruct or discover any source code, underlying ideas, algorithms, file formats or programming interfaces of the Software by any means whatsoever (except and only to the extent that applicable law prohibits or restricts reverse engineering restrictions); (b) distribute, sell, sublicense, rent, lease or use the Software for time sharing, hosting, service provider or like purposes, except as expressly permitted under this Agreement; (c) redistribute the Software or Modifications other than by including the Software or a portion thereof within your own product, which must have substantially different functionality than the Software or Modifications and must not allow any third party to use the Software or Modifications, or any portions thereof, for software development or application development purposes; (d) redistribute the Software as part of a product, "appliance" or "virtual server"; (e) redistribute the Software on any server which is not directly under your control; (f) remove any product identification, proprietary, copyright or other notices contained in the Software; (g) modify any part of the Software, create a derivative work of any part of the Software (except as permitted in Section 4), or incorporate the Software, except to the extent expressly authorized in writing by Contributed Systems; (h) publicly disseminate performance information or analysis (including, without limitation, benchmarks) from any source relating to the Software; (i) utilize any equipment, device, software, or other means designed to circumvent or remove any form of Source URL or copy protection used by Contributed Systems in connection with the Software, or use the Software together with any authorization code, Source URL, serial number, or other copy protection device not supplied by Contributed Systems; (j) use the Software to develop a product which is competitive with any Contributed Systems product offerings; or (k) use unauthorized Source URLS or keycode(s) or distribute or publish Source URLs or keycode(s), except as may be expressly permitted by Contributed Systems in writing. If your unique Source URL is ever published, Contributed Systems reserves the right to terminate your access without notice. 3.2 UNDER NO CIRCUMSTANCES MAY YOU USE THE SOFTWARE FOR A PRODUCT THAT IS INTENDED FOR SOFTWARE OR APPLICATION DEVELOPMENT PURPOSES. The Open Source version of the Software (“LGPL Version”) is licensed under the terms of the GNU Lesser General Public License versions 3.0 (“LGPL”) and not under this EULA. 4. Ownership. Notwithstanding anything to the contrary contained herein, except for the limited license rights expressly provided herein, Contributed Systems and its suppliers have and will retain all rights, title and interest (including, without limitation, all patent, copyright, trademark, trade secret and other intellectual property rights) in and to the Software and all copies, modifications and derivative works thereof (including any changes which incorporate any of your ideas, feedback or suggestions). You acknowledge that you are obtaining only a limited license right to the Software, and that irrespective of any use of the words “purchase”, “sale” or like terms hereunder no ownership rights are being conveyed to you under this Agreement or otherwise. 5. Fees and Payment. The Software license fees will be due and payable in full as set forth in the applicable invoice or at the time of purchase. If the Software does not function properly within two weeks of purchase, please contact us within those two weeks for a refund. You shall be responsible for all taxes, withholdings, duties and levies arising from the order (excluding taxes based on the net income of Contributed Systems). 6. Support, Maintenance and Services. Subject to the terms and conditions of this Agreement, as set forth in your invoice, and as set forth on the Sidekiq Pro support page (https://github.com/mperham/sidekiq/wiki/Pro-Support), support and maintenance services may be included with the purchase of your license subscription. 7. Term of Agreement. 7.1 Term. This Agreement is effective as of the Delivery of the Software and expires at such time as all license and service subscriptions hereunder have expired in accordance with their own terms (the “Term”). For clarification, the term of your license under this Agreement may be perpetual, limited for Evaluation Version, or designated as a fixed-term license in the Invoice, and shall be specified at your time of purchase. Either party may terminate this Agreement (including all related Invoices) if the other party: (a) fails to cure any material breach of this Agreement within thirty (30) days after written notice of such breach, provided that Contributed Systems may terminate this Agreement immediately upon any breach of Section 3 or if you exceed any other restrictions contained in Section 1, unless otherwise specified in this agreement; (b) ceases operation without a successor; or (c) seeks protection under any bankruptcy, receivership, trust deed, creditors arrangement, composition or comparable proceeding, or if any such proceeding is instituted against such party (and not dismissed within sixty (60) days)). Termination is not an exclusive remedy and the exercise by either party of any remedy under this Agreement will be without prejudice to any other remedies it may have under this Agreement, by law, or otherwise. 7.2 Termination. Upon any termination of this Agreement, you shall cease any and all use of any Software and destroy all copies thereof. 7.3 Expiration of License. Upon the expiration of any term under this Agreement, (a) all Software updates and services pursuant to the license shall cease, (b) you may only continue to run existing installations of the Software, (c) you may not install the Software on any additional Hosts, and (d) any new installation of the Software shall require the purchase of a new license subscription from Contributed Systems. 8. Disclaimer of Warranties. The Software is provided "as is," with all faults, defects and errors, and without warranty of any kind. Contributed Systems does not warrant that the Software will be free of bugs, errors, viruses or other defects, and Contributed Systems shall have no liability of any kind for the use of or inability to use the Software, the Software content or any associated service, and you acknowledge that it is not technically practicable for Contributed Systems to do so. To the maximum extent permitted by applicable law, Contributed Systems disclaims all warranties, express, implied, arising by law or otherwise, regarding the Software, the Software content and their respective performance or suitability for your intended use, including without limitation any implied warranty of merchantability, fitness for a particular purpose. 9. Limitation of Liability. In no event will Contributed Systems be liable for any direct, indirect, consequential, incidental, special, exemplary, or punitive damages or liabilities whatsoever arising from or relating to the Software, the Software content or this Agreement, whether based on contract, tort (including negligence), strict liability or other theory, even if Contributed Systems has been advised of the possibility of such damages. In no event will Contributed Systems' liability exceed the Software license price as indicated in the invoice. The existence of more than one claim will not enlarge or extend this limit. 10. Remedies. Your exclusive remedy and Contributed Systems’ entire liability for breach of this Agreement shall be limited, at Contributed Systems’ sole and exclusive discretion, to (a) replacement of any defective software or documentation; or (b) refund of the license fee paid to Contributed Systems, payable in accordance with Contributed Systems' refund policy. 11. Acknowledgements. 11.1 Consent to the Use of Data. You agree that Contributed Systems and its affiliates may collect and use technical information gathered as part of the product support services. Contributed Systems may use this information solely to improve products and services and will not disclose this information in a form that personally identifies you. 11.2 Verification. We or a certified auditor acting on our behalf, may, upon its reasonable request and at its expense, audit you with respect to the use of the Software. Such audit may be conducted by mail, electronic means or through an in-person visit to your place of business. Any such in-person audit shall be conducted during regular business hours at your facilities and shall not unreasonably interfere with your business activities. We shall not remove, copy, or redistribute any electronic material during the course of an audit. If an audit reveals that you are using the Software in a way that is in material violation of the terms of the EULA, then you shall pay our reasonable costs of conducting the audit. In the case of a material violation, you agree to pay Us any amounts owing that are attributable to the unauthorized use. In the alternative, We reserve the right, at our sole option, to terminate the licenses for the Software. 11.3 Government End Users. If the Software and related documentation are supplied to or purchased by or on behalf of the United States Government, then the Software is deemed to be "commercial software" as that term is used in the Federal Acquisition Regulation system. Rights of the United States shall not exceed the minimum rights set forth in FAR 52.227-19 for "restricted computer software". All other terms and conditions of this Agreement apply. 12. Third Party Software. Examples included in Software may provide links to third party libraries or code (collectively “Third Party Software”) to implement various functions. Third Party Software does not comprise part of the Software. In some cases, access to Third Party Software may be included along with the Software delivery as a convenience for demonstration purposes. Such source code and libraries may be included in the “…/examples” source tree delivered with the Software and do not comprise the Software. Licensee acknowledges (1) that some part of Third Party Software may require additional licensing of copyright and patents from the owners of such, and (2) that distribution of any of the Software referencing or including any portion of a Third Party Software may require appropriate licensing from such third parties. 13. Miscellaneous 13.1 Entire Agreement. This Agreement sets forth our entire agreement with respect to the Software and the subject matter hereof and supersedes all prior and contemporaneous understandings and agreements whether written or oral. 13.2 Amendment. Contributed Systems reserves the right, in its sole discretion, to amend this Agreement from time. Amendments to this Agreement can be located at: https://github.com/mperham/sidekiq/blob/master/COMM-LICENSE. 13.3 Assignment. You may not assign this Agreement or any of its rights under this Agreement without the prior written consent of Contributed Systems and any attempted assignment without such consent shall be void. 13.4 Export Compliance. You agree to comply with all applicable laws and regulations, including laws, regulations, orders or other restrictions on export, re-export or redistribution of software. 13.5 Indemnification. You agree to defend, indemnify, and hold harmless Contributed Systems from and against any lawsuits, claims, losses, damages, fines and expenses (including attorneys' fees and costs) arising out of your use of the Software or breach of this Agreement. 13.6 Governing Law. This Agreement is governed by the laws of the State of Oregon and the United States without regard to conflicts of laws provisions thereof, and without regard to the United Nations Convention on the International Sale of Goods or the Uniform Computer Information Transactions Act, as currently enacted by any jurisdiction or as may be codified or amended from time to time by any jurisdiction. The jurisdiction and venue for actions related to the subject matter hereof shall be the state of Oregon and United States federal courts located in Portland, Oregon, and both parties hereby submit to the personal jurisdiction of such courts. 13.7 Attorneys’ Fees and Costs. The prevailing party in any action to enforce this Agreement will be entitled to recover its attorneys’ fees and costs in connection with such action. 13.8 Severability. If any provision of this Agreement is held by a court of competent jurisdiction to be invalid, illegal, or unenforceable, the remainder of this Agreement will remain in full force and effect. 13.9 Waiver. Failure or neglect by either party to enforce at any time any of the provisions of this licence Agreement shall not be construed or deemed to be a waiver of that party's rights under this Agreement. 13.10 Headings. The headings of sections and paragraphs of this Agreement are for convenience of reference only and are not intended to restrict, affect or be of any weight in the interpretation or construction of the provisions of such sections or paragraphs. 14. Contact Information. If you have any questions about this EULA, or if you want to contact Contributed Systems for any reason, please direct correspondence to info@contribsys.com. sidekiq-4.0.1/test/0000755000175600017570000000000012631157272013173 5ustar pravipravisidekiq-4.0.1/test/test_redis_connection.rb0000644000175600017570000000677412631157272020122 0ustar pravipravirequire_relative 'helper' class TestRedisConnection < Sidekiq::Test describe ".create" do it "creates a pooled redis connection" do pool = Sidekiq::RedisConnection.create assert_equal Redis, pool.checkout.class end describe "network_timeout" do it "sets a custom network_timeout if specified" do pool = Sidekiq::RedisConnection.create(:network_timeout => 8) redis = pool.checkout assert_equal 8, redis.client.timeout end it "uses the default network_timeout if none specified" do pool = Sidekiq::RedisConnection.create redis = pool.checkout assert_equal 5, redis.client.timeout end end describe "namespace" do it "uses a given :namespace" do pool = Sidekiq::RedisConnection.create(:namespace => "xxx") assert_equal "xxx", pool.checkout.namespace end it "uses given :namespace over :namespace from Sidekiq.options" do Sidekiq.options[:namespace] = "xxx" pool = Sidekiq::RedisConnection.create(:namespace => "yyy") assert_equal "yyy", pool.checkout.namespace end end describe "socket path" do it "uses a given :path" do pool = Sidekiq::RedisConnection.create(:path => "/var/run/redis.sock") assert_equal "unix", pool.checkout.client.scheme assert_equal "redis:///var/run/redis.sock/0", pool.checkout.client.id end it "uses a given :path and :db" do pool = Sidekiq::RedisConnection.create(:path => "/var/run/redis.sock", :db => 8) assert_equal "unix", pool.checkout.client.scheme assert_equal "redis:///var/run/redis.sock/8", pool.checkout.client.id end end describe "pool_timeout" do it "uses a given :timeout over the default of 1" do pool = Sidekiq::RedisConnection.create(:pool_timeout => 5) assert_equal 5, pool.instance_eval{ @timeout } end it "uses the default timeout of 1 if no override" do pool = Sidekiq::RedisConnection.create assert_equal 1, pool.instance_eval{ @timeout } end end end describe ".determine_redis_provider" do before do @old_env = ENV.to_hash end after do ENV.update(@old_env) end def with_env_var(var, uri, skip_provider=false) vars = ['REDISTOGO_URL', 'REDIS_PROVIDER', 'REDIS_URL'] - [var] vars.each do |v| next if skip_provider ENV[v] = nil end ENV[var] = uri assert_equal uri, Sidekiq::RedisConnection.__send__(:determine_redis_provider) ENV[var] = nil end describe "with REDISTOGO_URL and a parallel REDIS_PROVIDER set" do it "sets connection URI to the provider" do uri = 'redis://sidekiq-redis-provider:6379/0' provider = 'SIDEKIQ_REDIS_PROVIDER' ENV['REDIS_PROVIDER'] = provider ENV[provider] = uri ENV['REDISTOGO_URL'] = 'redis://redis-to-go:6379/0' with_env_var provider, uri, true ENV[provider] = nil end end describe "with REDIS_PROVIDER set" do it "sets connection URI to the provider" do uri = 'redis://sidekiq-redis-provider:6379/0' provider = 'SIDEKIQ_REDIS_PROVIDER' ENV['REDIS_PROVIDER'] = provider ENV[provider] = uri with_env_var provider, uri, true ENV[provider] = nil end end describe "with REDIS_URL set" do it "sets connection URI to custom uri" do with_env_var 'REDIS_URL', 'redis://redis-uri:6379/0' end end end end sidekiq-4.0.1/test/test_launcher.rb0000644000175600017570000000406412631157272016364 0ustar pravipravirequire_relative 'helper' require 'sidekiq/launcher' class TestLauncher < Sidekiq::Test describe 'launcher' do before do Sidekiq.redis {|c| c.flushdb } end def new_manager(opts) Sidekiq::Manager.new(opts) end describe 'heartbeat' do before do uow = Object.new @mgr = new_manager(options) @launcher = Sidekiq::Launcher.new(options) @launcher.manager = @mgr Sidekiq::Processor::WORKER_STATE['a'] = {'b' => 1} @proctitle = $0 end after do Sidekiq::Processor::WORKER_STATE.clear $0 = @proctitle end describe 'when manager is active' do before do Sidekiq::CLI::PROCTITLES << proc { "xyz" } @launcher.heartbeat('identity', heartbeat_data, Sidekiq.dump_json(heartbeat_data)) Sidekiq::CLI::PROCTITLES.pop end it 'sets useful info to proctitle' do assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] xyz", $0 end it 'stores process info in redis' do info = Sidekiq.redis { |c| c.hmget('identity', 'busy') } assert_equal ["1"], info expires = Sidekiq.redis { |c| c.pttl('identity') } assert_in_delta 60000, expires, 500 end end describe 'when manager is stopped' do before do @launcher.quiet @launcher.heartbeat('identity', heartbeat_data, Sidekiq.dump_json(heartbeat_data)) end it 'indicates stopping status in proctitle' do assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] stopping", $0 end it 'stores process info in redis' do info = Sidekiq.redis { |c| c.hmget('identity', 'busy') } assert_equal ["1"], info expires = Sidekiq.redis { |c| c.pttl('identity') } assert_in_delta 60000, expires, 50 end end end def options { :concurrency => 3, :queues => ['default'] } end def heartbeat_data { 'concurrency' => 3, 'tag' => 'myapp' } end end end sidekiq-4.0.1/test/test_extensions.rb0000644000175600017570000000670612631157272016767 0ustar pravipravirequire_relative 'helper' require 'sidekiq' require 'active_record' require 'action_mailer' require 'sidekiq/extensions/action_mailer' require 'sidekiq/extensions/active_record' require 'sidekiq/rails' Sidekiq.hook_rails! class TestExtensions < Sidekiq::Test describe 'sidekiq extensions' do before do Sidekiq.redis = REDIS Sidekiq.redis {|c| c.flushdb } end class MyModel < ActiveRecord::Base def self.long_class_method raise "Should not be called!" end end it 'allows delayed execution of ActiveRecord class methods' do assert_equal [], Sidekiq::Queue.all.map(&:name) q = Sidekiq::Queue.new assert_equal 0, q.size MyModel.delay.long_class_method assert_equal ['default'], Sidekiq::Queue.all.map(&:name) assert_equal 1, q.size end it 'uses and stringifies specified options' do assert_equal [], Sidekiq::Queue.all.map(&:name) q = Sidekiq::Queue.new('notdefault') assert_equal 0, q.size MyModel.delay(queue: :notdefault).long_class_method assert_equal ['notdefault'], Sidekiq::Queue.all.map(&:name) assert_equal 1, q.size end it 'allows delayed scheduling of AR class methods' do ss = Sidekiq::ScheduledSet.new assert_equal 0, ss.size MyModel.delay_for(5.days).long_class_method assert_equal 1, ss.size end it 'allows until delayed scheduling of AR class methods' do ss = Sidekiq::ScheduledSet.new assert_equal 0, ss.size MyModel.delay_until(1.day.from_now).long_class_method assert_equal 1, ss.size end class UserMailer < ActionMailer::Base def greetings(a, b) raise "Should not be called!" end end it 'allows delayed delivery of ActionMailer mails' do assert_equal [], Sidekiq::Queue.all.map(&:name) q = Sidekiq::Queue.new assert_equal 0, q.size UserMailer.delay.greetings(1, 2) assert_equal ['default'], Sidekiq::Queue.all.map(&:name) assert_equal 1, q.size end it 'allows delayed scheduling of AM mails' do ss = Sidekiq::ScheduledSet.new assert_equal 0, ss.size UserMailer.delay_for(5.days).greetings(1, 2) assert_equal 1, ss.size end it 'allows until delay scheduling of AM mails' do ss = Sidekiq::ScheduledSet.new assert_equal 0, ss.size UserMailer.delay_until(5.days.from_now).greetings(1, 2) assert_equal 1, ss.size end class SomeClass def self.doit(arg) end end it 'allows delay of any ole class method' do q = Sidekiq::Queue.new assert_equal 0, q.size SomeClass.delay.doit(Date.today) assert_equal 1, q.size end module SomeModule def self.doit(arg) end end it 'allows delay of any module class method' do q = Sidekiq::Queue.new assert_equal 0, q.size SomeModule.delay.doit(Date.today) assert_equal 1, q.size end it 'allows removing of the #delay methods' do q = Sidekiq::Queue.new Sidekiq.remove_delay! assert_equal 0, q.size assert_raises NoMethodError do SomeModule.delay.doit(Date.today) end Sidekiq.instance_eval { remove_instance_variable :@delay_removed } # Reload modified modules load 'sidekiq/extensions/action_mailer.rb' load 'sidekiq/extensions/active_record.rb' load 'sidekiq/extensions/generic_proxy.rb' load 'sidekiq/extensions/class_methods.rb' end end end sidekiq-4.0.1/test/test_client.rb0000644000175600017570000001367212631157272016046 0ustar pravipravirequire_relative 'helper' class TestClient < Sidekiq::Test describe 'errors' do it 'raises ArgumentError with invalid params' do assert_raises ArgumentError do Sidekiq::Client.push('foo', 1) end assert_raises ArgumentError do Sidekiq::Client.push('foo', :class => 'Foo', :noargs => [1, 2]) end assert_raises ArgumentError do Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'noargs' => [1, 2]) end assert_raises ArgumentError do Sidekiq::Client.push('queue' => 'foo', 'class' => 42, 'args' => [1, 2]) end assert_raises ArgumentError do Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => 1) end end end describe 'as instance' do it 'can push' do client = Sidekiq::Client.new jid = client.push('class' => 'Blah', 'args' => [1,2,3]) assert_equal 24, jid.size end it 'allows local middleware modification' do $called = false mware = Class.new { def call(worker_klass,msg,q,r); $called = true; msg;end } client = Sidekiq::Client.new client.middleware do |chain| chain.add mware end client.push('class' => 'Blah', 'args' => [1,2,3]) assert $called assert client.middleware.exists?(mware) refute Sidekiq.client_middleware.exists?(mware) end end describe 'client' do it 'pushes messages to redis' do q = Sidekiq::Queue.new('foo') pre = q.size jid = Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => [1, 2]) assert jid assert_equal 24, jid.size assert_equal pre + 1, q.size end it 'pushes messages to redis using a String class' do q = Sidekiq::Queue.new('foo') pre = q.size jid = Sidekiq::Client.push('queue' => 'foo', 'class' => 'MyWorker', 'args' => [1, 2]) assert jid assert_equal 24, jid.size assert_equal pre + 1, q.size end class MyWorker include Sidekiq::Worker end class QueuedWorker include Sidekiq::Worker sidekiq_options :queue => :flimflam end it 'enqueues' do Sidekiq.redis {|c| c.flushdb } assert_equal Sidekiq.default_worker_options, MyWorker.get_sidekiq_options assert MyWorker.perform_async(1, 2) assert Sidekiq::Client.enqueue(MyWorker, 1, 2) assert Sidekiq::Client.enqueue_to(:custom_queue, MyWorker, 1, 2) assert_equal 1, Sidekiq::Queue.new('custom_queue').size assert Sidekiq::Client.enqueue_to_in(:custom_queue, 3.minutes, MyWorker, 1, 2) assert Sidekiq::Client.enqueue_to_in(:custom_queue, -3.minutes, MyWorker, 1, 2) assert_equal 2, Sidekiq::Queue.new('custom_queue').size assert Sidekiq::Client.enqueue_in(3.minutes, MyWorker, 1, 2) assert QueuedWorker.perform_async(1, 2) assert_equal 1, Sidekiq::Queue.new('flimflam').size end end describe 'bulk' do after do Sidekiq::Queue.new.clear end it 'can push a large set of jobs at once' do jids = Sidekiq::Client.push_bulk('class' => QueuedWorker, 'args' => (1..1_000).to_a.map { |x| Array(x) }) assert_equal 1_000, jids.size end it 'can push a large set of jobs at once using a String class' do jids = Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => (1..1_000).to_a.map { |x| Array(x) }) assert_equal 1_000, jids.size end it 'returns the jids for the jobs' do Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => (1..2).to_a.map { |x| Array(x) }).each do |jid| assert_match(/[0-9a-f]{12}/, jid) end end end class BaseWorker include Sidekiq::Worker sidekiq_options 'retry' => 'base' end class AWorker < BaseWorker end class BWorker < BaseWorker sidekiq_options 'retry' => 'b' end class CWorker < BaseWorker sidekiq_options 'retry' => 2 end describe 'client middleware' do class Stopper def call(worker_class, job, queue, r) raise ArgumentError unless r yield if job['args'].first.odd? end end it 'can stop some of the jobs from pushing' do client = Sidekiq::Client.new client.middleware do |chain| chain.add Stopper end assert_equal nil, client.push('class' => MyWorker, 'args' => [0]) assert_match(/[0-9a-f]{12}/, client.push('class' => MyWorker, 'args' => [1])) client.push_bulk('class' => MyWorker, 'args' => [[0], [1]]).each do |jid| assert_match(/[0-9a-f]{12}/, jid) end end end describe 'inheritance' do it 'inherits sidekiq options' do assert_equal 'base', AWorker.get_sidekiq_options['retry'] assert_equal 'b', BWorker.get_sidekiq_options['retry'] end end describe 'sharding' do class DWorker < BaseWorker end it 'allows sidekiq_options to point to different Redi' do conn = MiniTest::Mock.new conn.expect(:multi, [0, 1]) DWorker.sidekiq_options('pool' => ConnectionPool.new(size: 1) { conn }) DWorker.perform_async(1,2,3) conn.verify end it 'allows #via to point to different Redi' do conn = MiniTest::Mock.new conn.expect(:multi, [0, 1]) default = Sidekiq::Client.new.redis_pool sharded_pool = ConnectionPool.new(size: 1) { conn } Sidekiq::Client.via(sharded_pool) do CWorker.perform_async(1,2,3) assert_equal sharded_pool, Sidekiq::Client.new.redis_pool assert_raises RuntimeError do Sidekiq::Client.via(default) do # nothing end end end assert_equal default, Sidekiq::Client.new.redis_pool conn.verify end it 'allows Resque helpers to point to different Redi' do conn = MiniTest::Mock.new conn.expect(:multi, []) { |*args, &block| block.call } conn.expect(:zadd, 1, [String, Array]) DWorker.sidekiq_options('pool' => ConnectionPool.new(size: 1) { conn }) Sidekiq::Client.enqueue_in(10, DWorker, 3) conn.verify end end end sidekiq-4.0.1/test/test_scheduling.rb0000644000175600017570000000233212631157272016704 0ustar pravipravirequire_relative 'helper' require 'sidekiq/scheduled' class TestScheduling < Sidekiq::Test describe 'middleware' do class ScheduledWorker include Sidekiq::Worker sidekiq_options :queue => :custom_queue def perform(x) end end it 'schedules jobs' do ss = Sidekiq::ScheduledSet.new ss.clear assert_equal 0, ss.size assert ScheduledWorker.perform_in(600, 'mike') assert_equal 1, ss.size assert ScheduledWorker.perform_in(1.month, 'mike') assert_equal 2, ss.size assert ScheduledWorker.perform_in(5.days.from_now, 'mike') assert_equal 3, ss.size q = Sidekiq::Queue.new("custom_queue") qs = q.size assert ScheduledWorker.perform_in(-300, 'mike') assert_equal 3, ss.size assert_equal qs+1, q.size assert Sidekiq::Client.push_bulk('class' => ScheduledWorker, 'args' => [['mike'], ['mike']], 'at' => 600) assert_equal 5, ss.size end it 'removes the enqueued_at field when scheduling' do ss = Sidekiq::ScheduledSet.new ss.clear assert ScheduledWorker.perform_in(1.month, 'mike') job = ss.first assert job['created_at'] refute job['enqueued_at'] end end end sidekiq-4.0.1/test/test_testing_fake.rb0000644000175600017570000001471112631157272017226 0ustar pravipravirequire_relative 'helper' require 'active_record' require 'action_mailer' require 'sidekiq/rails' require 'sidekiq/extensions/action_mailer' require 'sidekiq/extensions/active_record' Sidekiq.hook_rails! class TestTesting < Sidekiq::Test describe 'sidekiq testing' do class PerformError < RuntimeError; end class DirectWorker include Sidekiq::Worker def perform(a, b) a + b end end class EnqueuedWorker include Sidekiq::Worker def perform(a, b) a + b end end class StoredWorker include Sidekiq::Worker def perform(error) raise PerformError if error end end class FooMailer < ActionMailer::Base def bar(str) str end end class FooModel < ActiveRecord::Base def bar(str) str end end before do require 'sidekiq/testing' Sidekiq::Testing.fake! EnqueuedWorker.jobs.clear DirectWorker.jobs.clear end after do Sidekiq::Testing.disable! end it 'stubs the async call' do assert_equal 0, DirectWorker.jobs.size assert DirectWorker.perform_async(1, 2) assert_equal 1, DirectWorker.jobs.size assert DirectWorker.perform_in(10, 1, 2) assert_equal 2, DirectWorker.jobs.size assert DirectWorker.perform_at(10, 1, 2) assert_equal 3, DirectWorker.jobs.size assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.01 end it 'stubs the delay call on mailers' do assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size FooMailer.delay.bar('hello!') assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size end class Something def self.foo(x) end end it 'stubs the delay call on models' do assert_equal 0, Sidekiq::Extensions::DelayedClass.jobs.size Something.delay.foo(Date.today) assert_equal 1, Sidekiq::Extensions::DelayedClass.jobs.size end it 'stubs the enqueue call' do assert_equal 0, EnqueuedWorker.jobs.size assert Sidekiq::Client.enqueue(EnqueuedWorker, 1, 2) assert_equal 1, EnqueuedWorker.jobs.size end it 'stubs the enqueue_to call' do assert_equal 0, EnqueuedWorker.jobs.size assert Sidekiq::Client.enqueue_to('someq', EnqueuedWorker, 1, 2) assert_equal 1, EnqueuedWorker.jobs.size end it 'executes all stored jobs' do assert StoredWorker.perform_async(false) assert StoredWorker.perform_async(true) assert_equal 2, StoredWorker.jobs.size assert_raises PerformError do StoredWorker.drain end assert_equal 0, StoredWorker.jobs.size end class SpecificJidWorker include Sidekiq::Worker class_attribute :count self.count = 0 def perform(worker_jid) return unless worker_jid == self.jid self.class.count += 1 end end it 'execute only jobs with assigned JID' do 4.times do |i| jid = SpecificJidWorker.perform_async(nil) if i % 2 == 0 SpecificJidWorker.jobs[-1]["args"] = ["wrong_jid"] else SpecificJidWorker.jobs[-1]["args"] = [jid] end end SpecificJidWorker.perform_one assert_equal 0, SpecificJidWorker.count SpecificJidWorker.perform_one assert_equal 1, SpecificJidWorker.count SpecificJidWorker.drain assert_equal 2, SpecificJidWorker.count end it 'round trip serializes the job arguments' do assert StoredWorker.perform_async(:mike) job = StoredWorker.jobs.first assert_equal "mike", job['args'].first StoredWorker.clear end it 'perform_one runs only one job' do DirectWorker.perform_async(1, 2) DirectWorker.perform_async(3, 4) assert_equal 2, DirectWorker.jobs.size DirectWorker.perform_one assert_equal 1, DirectWorker.jobs.size DirectWorker.clear end it 'perform_one raise error upon empty queue' do DirectWorker.clear assert_raises Sidekiq::EmptyQueueError do DirectWorker.perform_one end end class FirstWorker include Sidekiq::Worker class_attribute :count self.count = 0 def perform self.class.count += 1 end end class SecondWorker include Sidekiq::Worker class_attribute :count self.count = 0 def perform self.class.count += 1 end end class ThirdWorker include Sidekiq::Worker class_attribute :count def perform FirstWorker.perform_async SecondWorker.perform_async end end it 'clears jobs across all workers' do Sidekiq::Worker.jobs.clear FirstWorker.count = 0 SecondWorker.count = 0 assert_equal 0, FirstWorker.jobs.size assert_equal 0, SecondWorker.jobs.size FirstWorker.perform_async SecondWorker.perform_async assert_equal 1, FirstWorker.jobs.size assert_equal 1, SecondWorker.jobs.size Sidekiq::Worker.clear_all assert_equal 0, FirstWorker.jobs.size assert_equal 0, SecondWorker.jobs.size assert_equal 0, FirstWorker.count assert_equal 0, SecondWorker.count end it 'drains jobs across all workers' do Sidekiq::Worker.jobs.clear FirstWorker.count = 0 SecondWorker.count = 0 assert_equal 0, FirstWorker.jobs.size assert_equal 0, SecondWorker.jobs.size assert_equal 0, FirstWorker.count assert_equal 0, SecondWorker.count FirstWorker.perform_async SecondWorker.perform_async assert_equal 1, FirstWorker.jobs.size assert_equal 1, SecondWorker.jobs.size Sidekiq::Worker.drain_all assert_equal 0, FirstWorker.jobs.size assert_equal 0, SecondWorker.jobs.size assert_equal 1, FirstWorker.count assert_equal 1, SecondWorker.count end it 'drains jobs across all workers even when workers create new jobs' do Sidekiq::Worker.jobs.clear FirstWorker.count = 0 SecondWorker.count = 0 assert_equal 0, ThirdWorker.jobs.size assert_equal 0, FirstWorker.count assert_equal 0, SecondWorker.count ThirdWorker.perform_async assert_equal 1, ThirdWorker.jobs.size Sidekiq::Worker.drain_all assert_equal 0, ThirdWorker.jobs.size assert_equal 1, FirstWorker.count assert_equal 1, SecondWorker.count end it 'can execute a job' do DirectWorker.execute_job(DirectWorker.new, [2, 3]) end end end sidekiq-4.0.1/test/test_processor.rb0000644000175600017570000001317512631157272016605 0ustar pravipravirequire_relative 'helper' require 'sidekiq/fetch' require 'sidekiq/cli' require 'sidekiq/processor' class TestProcessor < Sidekiq::Test TestException = Class.new(StandardError) TEST_EXCEPTION = TestException.new("kerboom!") describe 'processor' do before do $invokes = 0 @mgr = Minitest::Mock.new @mgr.expect(:options, {:queues => ['default']}) @mgr.expect(:options, {:queues => ['default']}) @processor = ::Sidekiq::Processor.new(@mgr) end class MockWorker include Sidekiq::Worker def perform(args) raise TEST_EXCEPTION if args == 'boom' args.pop if args.is_a? Array $invokes += 1 end end def work(msg, queue='queue:default') Sidekiq::BasicFetch::UnitOfWork.new(queue, msg) end it 'processes as expected' do msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) @processor.process(work(msg)) assert_equal 1, $invokes end it 'executes a worker as expected' do worker = Minitest::Mock.new worker.expect(:perform, nil, [1, 2, 3]) @processor.execute_job(worker, [1, 2, 3]) end it 're-raises exceptions after handling' do msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] }) re_raise = false begin @processor.process(work(msg)) flunk "Expected exception" rescue TestException re_raise = true end assert_equal 0, $invokes assert re_raise, "does not re-raise exceptions after handling" end it 'does not modify original arguments' do msg = { 'class' => MockWorker.to_s, 'args' => [['myarg']] } msgstr = Sidekiq.dump_json(msg) @mgr.expect(:processor_done, nil, [@processor]) @processor.process(work(msgstr)) assert_equal [['myarg']], msg['args'] end describe 'acknowledgement' do class ExceptionRaisingMiddleware def initialize(raise_before_yield, raise_after_yield, skip) @raise_before_yield = raise_before_yield @raise_after_yield = raise_after_yield @skip = skip end def call(worker, item, queue) raise TEST_EXCEPTION if @raise_before_yield yield unless @skip raise TEST_EXCEPTION if @raise_after_yield end end let(:raise_before_yield) { false } let(:raise_after_yield) { false } let(:skip_job) { false } let(:worker_args) { ['myarg'] } let(:work) { MiniTest::Mock.new } before do work.expect(:queue_name, 'queue:default') work.expect(:job, Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => worker_args })) Sidekiq.server_middleware do |chain| chain.prepend ExceptionRaisingMiddleware, raise_before_yield, raise_after_yield, skip_job end end after do Sidekiq.server_middleware do |chain| chain.remove ExceptionRaisingMiddleware end work.verify end describe 'middleware throws an exception before processing the work' do let(:raise_before_yield) { true } it 'does not ack' do begin @processor.process(work) flunk "Expected #process to raise exception" rescue TestException end end end describe 'middleware throws an exception after processing the work' do let(:raise_after_yield) { true } it 'acks the job' do work.expect(:acknowledge, nil) begin @processor.process(work) flunk "Expected #process to raise exception" rescue TestException end end end describe 'middleware decides to skip work' do let(:skip_job) { true } it 'acks the job' do work.expect(:acknowledge, nil) @mgr.expect(:processor_done, nil, [@processor]) @processor.process(work) end end describe 'worker raises an exception' do let(:worker_args) { ['boom'] } it 'acks the job' do work.expect(:acknowledge, nil) begin @processor.process(work) flunk "Expected #process to raise exception" rescue TestException end end end describe 'everything goes well' do it 'acks the job' do work.expect(:acknowledge, nil) @mgr.expect(:processor_done, nil, [@processor]) @processor.process(work) end end end describe 'stats' do before do Sidekiq.redis {|c| c.flushdb } end describe 'when successful' do let(:processed_today_key) { "stat:processed:#{Time.now.utc.strftime("%Y-%m-%d")}" } def successful_job msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) @mgr.expect(:processor_done, nil, [@processor]) @processor.process(work(msg)) end it 'increments processed stat' do Sidekiq::Processor::PROCESSED.value = 0 successful_job assert_equal 1, Sidekiq::Processor::PROCESSED.value end end describe 'when failed' do let(:failed_today_key) { "stat:failed:#{Time.now.utc.strftime("%Y-%m-%d")}" } def failed_job msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] }) begin @processor.process(work(msg)) rescue TestException end end it 'increments failed stat' do Sidekiq::Processor::FAILURE.value = 0 failed_job assert_equal 1, Sidekiq::Processor::FAILURE.value end end end end end sidekiq-4.0.1/test/test_fetch.rb0000644000175600017570000000262512631157272015655 0ustar pravipravirequire_relative 'helper' require 'sidekiq/fetch' class TestFetcher < Sidekiq::Test describe 'fetcher' do before do Sidekiq.redis = { :url => REDIS_URL, :namespace => 'fuzzy' } Sidekiq.redis do |conn| conn.flushdb conn.rpush('queue:basic', 'msg') end end after do Sidekiq.redis = REDIS end it 'retrieves' do fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar']) uow = fetch.retrieve_work refute_nil uow assert_equal 'basic', uow.queue_name assert_equal 'msg', uow.job q = Sidekiq::Queue.new('basic') assert_equal 0, q.size uow.requeue assert_equal 1, q.size assert_nil uow.acknowledge end it 'retrieves with strict setting' do fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar', 'bar'], :strict => true) cmd = fetch.queues_cmd assert_equal cmd, ['queue:basic', 'queue:bar', Sidekiq::BasicFetch::TIMEOUT] end it 'bulk requeues' do q1 = Sidekiq::Queue.new('foo') q2 = Sidekiq::Queue.new('bar') assert_equal 0, q1.size assert_equal 0, q2.size uow = Sidekiq::BasicFetch::UnitOfWork Sidekiq::BasicFetch.bulk_requeue([uow.new('fuzzy:queue:foo', 'bob'), uow.new('fuzzy:queue:foo', 'bar'), uow.new('fuzzy:queue:bar', 'widget')], {:queues => []}) assert_equal 2, q1.size assert_equal 1, q2.size end end end sidekiq-4.0.1/test/fixtures/0000755000175600017570000000000012631157272015044 5ustar pravipravisidekiq-4.0.1/test/fixtures/en.yml0000644000175600017570000000006612631157272016173 0ustar pravipravien: translated_text: 'Changed text from add locals' sidekiq-4.0.1/test/config.yml0000644000175600017570000000031512631157272015162 0ustar pravipravi--- :verbose: false :require: ./test/fake_env.rb :pidfile: /tmp/sidekiq-config-test.pid :logfile: /tmp/sidekiq.log :concurrency: 50 :queues: - [<%="very_"%>often, 2] - [seldom, 1] sidekiq-4.0.1/test/test_cli.rb0000644000175600017570000002504712631157272015336 0ustar pravipravirequire_relative 'helper' require 'sidekiq/cli' require 'tempfile' class Sidekiq::CLI def die(code) @code = code end def valid? !@code end end class TestCli < Sidekiq::Test describe 'CLI#parse' do before do @cli = Sidekiq::CLI.new @opts = Sidekiq.options.dup end after do Sidekiq.options = @opts end it 'does not require the specified Ruby code' do @cli.parse(['sidekiq', '-r', './test/fake_env.rb']) refute($LOADED_FEATURES.any? { |x| x =~ /fake_env/ }) assert @cli.valid? end it 'does not boot rails' do refute defined?(::Rails::Application) @cli.parse(['sidekiq', '-r', './myapp']) refute defined?(::Rails::Application) end it 'changes concurrency' do @cli.parse(['sidekiq', '-c', '60', '-r', './test/fake_env.rb']) assert_equal 60, Sidekiq.options[:concurrency] end it 'changes queues' do @cli.parse(['sidekiq', '-q', 'foo', '-r', './test/fake_env.rb']) assert_equal ['foo'], Sidekiq.options[:queues] end it 'accepts a process index' do @cli.parse(['sidekiq', '-i', '7', '-r', './test/fake_env.rb']) assert_equal 7, Sidekiq.options[:index] end it 'accepts a stringy process index' do @cli.parse(['sidekiq', '-i', 'worker.7', '-r', './test/fake_env.rb']) assert_equal 7, Sidekiq.options[:index] end it 'sets strictly ordered queues if weights are not present' do @cli.parse(['sidekiq', '-q', 'foo', '-q', 'bar', '-r', './test/fake_env.rb']) assert_equal true, !!Sidekiq.options[:strict] end it 'does not set strictly ordered queues if weights are present' do @cli.parse(['sidekiq', '-q', 'foo,3', '-r', './test/fake_env.rb']) assert_equal false, !!Sidekiq.options[:strict] end it 'does not set strictly ordered queues if weights are present with multiple queues' do @cli.parse(['sidekiq', '-q', 'foo,3', '-q', 'bar', '-r', './test/fake_env.rb']) assert_equal false, !!Sidekiq.options[:strict] end it 'changes timeout' do @cli.parse(['sidekiq', '-t', '30', '-r', './test/fake_env.rb']) assert_equal 30, Sidekiq.options[:timeout] end it 'handles multiple queues with weights' do @cli.parse(['sidekiq', '-q', 'foo,3', '-q', 'bar', '-r', './test/fake_env.rb']) assert_equal %w(foo foo foo bar), Sidekiq.options[:queues] end it 'handles queues with multi-word names' do @cli.parse(['sidekiq', '-q', 'queue_one', '-q', 'queue-two', '-r', './test/fake_env.rb']) assert_equal %w(queue_one queue-two), Sidekiq.options[:queues] end it 'handles queues with dots in the name' do @cli.parse(['sidekiq', '-q', 'foo.bar', '-r', './test/fake_env.rb']) assert_equal ['foo.bar'], Sidekiq.options[:queues] end it 'sets verbose' do old = Sidekiq.logger.level @cli.parse(['sidekiq', '-v', '-r', './test/fake_env.rb']) assert_equal Logger::DEBUG, Sidekiq.logger.level # If we leave the logger at DEBUG it'll add a lot of noise to the test output Sidekiq.options.delete(:verbose) Sidekiq.logger.level = old end describe 'with logfile' do before do @old_logger = Sidekiq.logger @tmp_log_path = '/tmp/sidekiq.log' end after do Sidekiq.logger = @old_logger Sidekiq.options.delete(:logfile) File.unlink @tmp_log_path if File.exist?(@tmp_log_path) end it 'sets the logfile path' do @cli.parse(['sidekiq', '-L', @tmp_log_path, '-r', './test/fake_env.rb']) assert_equal @tmp_log_path, Sidekiq.options[:logfile] end it 'creates and writes to a logfile' do @cli.parse(['sidekiq', '-L', @tmp_log_path, '-r', './test/fake_env.rb']) Sidekiq.logger.info('test message') assert_match(/test message/, File.read(@tmp_log_path), "didn't include the log message") end it 'appends messages to a logfile' do File.open(@tmp_log_path, 'w') do |f| f.puts 'already existent log message' end @cli.parse(['sidekiq', '-L', @tmp_log_path, '-r', './test/fake_env.rb']) Sidekiq.logger.info('test message') log_file_content = File.read(@tmp_log_path) assert_match(/already existent/, log_file_content, "didn't include the old message") assert_match(/test message/, log_file_content, "didn't include the new message") end end describe 'with pidfile' do before do @tmp_file = Tempfile.new('sidekiq-test') @tmp_path = @tmp_file.path @tmp_file.close! @cli.parse(['sidekiq', '-P', @tmp_path, '-r', './test/fake_env.rb']) end after do File.unlink @tmp_path if File.exist? @tmp_path end it 'sets pidfile path' do assert_equal @tmp_path, Sidekiq.options[:pidfile] end it 'writes pidfile' do assert_equal File.read(@tmp_path).strip.to_i, Process.pid end end describe 'with config file' do before do @cli.parse(['sidekiq', '-C', './test/config.yml']) end it 'parses as expected' do assert_equal './test/config.yml', Sidekiq.options[:config_file] refute Sidekiq.options[:verbose] assert_equal './test/fake_env.rb', Sidekiq.options[:require] assert_equal nil, Sidekiq.options[:environment] assert_equal 50, Sidekiq.options[:concurrency] assert_equal '/tmp/sidekiq-config-test.pid', Sidekiq.options[:pidfile] assert_equal '/tmp/sidekiq.log', Sidekiq.options[:logfile] assert_equal 2, Sidekiq.options[:queues].count { |q| q == 'very_often' } assert_equal 1, Sidekiq.options[:queues].count { |q| q == 'seldom' } end end describe 'with env based config file' do before do @cli.parse(['sidekiq', '-e', 'staging', '-C', './test/env_based_config.yml']) end it 'parses as expected' do assert_equal './test/env_based_config.yml', Sidekiq.options[:config_file] refute Sidekiq.options[:verbose] assert_equal './test/fake_env.rb', Sidekiq.options[:require] assert_equal 'staging', Sidekiq.options[:environment] assert_equal 5, Sidekiq.options[:concurrency] assert_equal '/tmp/sidekiq-config-test.pid', Sidekiq.options[:pidfile] assert_equal '/tmp/sidekiq.log', Sidekiq.options[:logfile] assert_equal 2, Sidekiq.options[:queues].count { |q| q == 'very_often' } assert_equal 1, Sidekiq.options[:queues].count { |q| q == 'seldom' } end end describe 'with an empty config file' do before do @tmp_file = Tempfile.new('sidekiq-test') @tmp_path = @tmp_file.path @tmp_file.close! end after do File.unlink @tmp_path if File.exist? @tmp_path end it 'takes a path' do @cli.parse(['sidekiq', '-C', @tmp_path]) assert_equal @tmp_path, Sidekiq.options[:config_file] end it 'should have an identical options hash, except for config_file' do @cli.parse(['sidekiq']) old_options = Sidekiq.options.clone @cli.parse(['sidekiq', '-C', @tmp_path]) new_options = Sidekiq.options.clone refute_equal old_options, new_options new_options.delete(:config_file) assert_equal old_options, new_options end end describe 'with config file and flags' do before do # We need an actual file here. @tmp_lib_path = '/tmp/require-me.rb' File.open(@tmp_lib_path, 'w') do |f| f.puts "# do work" end @tmp_file = Tempfile.new('sidekiqr') @tmp_path = @tmp_file.path @tmp_file.close! @cli.parse(['sidekiq', '-C', './test/config.yml', '-e', 'snoop', '-c', '100', '-r', @tmp_lib_path, '-P', @tmp_path, '-q', 'often,7', '-q', 'seldom,3']) end after do File.unlink @tmp_lib_path if File.exist? @tmp_lib_path File.unlink @tmp_path if File.exist? @tmp_path end it 'gives the expected options' do assert_equal 100, Sidekiq.options[:concurrency] assert_equal @tmp_lib_path, Sidekiq.options[:require] assert_equal 'snoop', Sidekiq.options[:environment] assert_equal @tmp_path, Sidekiq.options[:pidfile] assert_equal 7, Sidekiq.options[:queues].count { |q| q == 'often' } assert_equal 3, Sidekiq.options[:queues].count { |q| q == 'seldom' } end end describe 'Sidekiq::CLI#parse_queues' do describe 'when weight is present' do it 'concatenates queues by factor of weight and sets strict to false' do opts = { strict: true } @cli.__send__ :parse_queues, opts, [['often', 7], ['repeatedly', 3]] @cli.__send__ :parse_queues, opts, [['once']] assert_equal (%w[often] * 7 + %w[repeatedly] * 3 + %w[once]), opts[:queues] assert !opts[:strict] end end describe 'when weight is not present' do it 'returns queues and sets strict' do opts = { strict: true } @cli.__send__ :parse_queues, opts, [['once'], ['one_time']] @cli.__send__ :parse_queues, opts, [['einmal']] assert_equal %w[once one_time einmal], opts[:queues] assert opts[:strict] end end end describe 'Sidekiq::CLI#parse_queue' do describe 'when weight is present' do it 'concatenates queue to opts[:queues] weight number of times and sets strict to false' do opts = { strict: true } @cli.__send__ :parse_queue, opts, 'often', 7 assert_equal %w[often] * 7, opts[:queues] assert !opts[:strict] end end describe 'when weight is not present' do it 'concatenates queue to opts[:queues] once and leaves strict true' do opts = { strict: true } @cli.__send__ :parse_queue, opts, 'once', nil assert_equal %w[once], opts[:queues] assert opts[:strict] end end end end describe 'misc' do it 'handles interrupts' do cli = Sidekiq::CLI.new assert_raises Interrupt do cli.handle_signal('INT') end assert_raises Interrupt do cli.handle_signal('TERM') end cli.handle_signal('USR2') cli.handle_signal('TTIN') end it 'can fire events' do count = 0 Sidekiq.options[:lifecycle_events][:startup] = [proc { count += 1 }] cli = Sidekiq::CLI.new cli.fire_event(:startup) assert_equal 1, count end end end sidekiq-4.0.1/test/test_exception_handler.rb0000644000175600017570000000273012631157272020254 0ustar pravipravirequire_relative 'helper' require 'sidekiq/exception_handler' require 'stringio' require 'logger' ExceptionHandlerTestException = Class.new(StandardError) TEST_EXCEPTION = ExceptionHandlerTestException.new("Something didn't work!") class Component include Sidekiq::ExceptionHandler def invoke_exception(args) raise TEST_EXCEPTION rescue ExceptionHandlerTestException => e handle_exception(e,args) end end class TestExceptionHandler < Sidekiq::Test describe "with mock logger" do before do @old_logger = Sidekiq.logger @str_logger = StringIO.new Sidekiq.logger = Logger.new(@str_logger) end after do Sidekiq.logger = @old_logger end it "logs the exception to Sidekiq.logger" do Component.new.invoke_exception(:a => 1) @str_logger.rewind log = @str_logger.readlines assert_match(/a=>1/, log[0], "didn't include the context") assert_match(/Something didn't work!/, log[1], "didn't include the exception message") assert_match(/test\/test_exception_handler.rb/, log[2], "didn't include the backtrace") end describe "when the exception does not have a backtrace" do it "does not fail" do exception = ExceptionHandlerTestException.new assert_nil exception.backtrace begin Component.new.handle_exception exception pass rescue StandardError flunk "failed handling a nil backtrace" end end end end end sidekiq-4.0.1/test/test_retry.rb0000644000175600017570000002243212631157272015727 0ustar pravipravi# encoding: utf-8 require_relative 'helper' require 'sidekiq/scheduled' require 'sidekiq/middleware/server/retry_jobs' class TestRetry < Sidekiq::Test describe 'middleware' do class SomeWorker include Sidekiq::Worker end before do Sidekiq.redis {|c| c.flushdb } end def worker @worker ||= SomeWorker.new end def handler(options={}) @handler ||= Sidekiq::Middleware::Server::RetryJobs.new(options) end def job(options={}) @job ||= { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }.merge(options) end it 'allows disabling retry' do assert_raises RuntimeError do handler.call(worker, job('retry' => false), 'default') do raise "kerblammo!" end end assert_equal 0, Sidekiq::RetrySet.new.size end it 'allows a numeric retry' do assert_raises RuntimeError do handler.call(worker, job('retry' => 2), 'default') do raise "kerblammo!" end end assert_equal 1, Sidekiq::RetrySet.new.size assert_equal 0, Sidekiq::DeadSet.new.size end it 'allows 0 retry => no retry and dead queue' do assert_raises RuntimeError do handler.call(worker, job('retry' => 0), 'default') do raise "kerblammo!" end end assert_equal 0, Sidekiq::RetrySet.new.size assert_equal 1, Sidekiq::DeadSet.new.size end it 'handles zany characters in error message, #1705' do skip 'skipped! test requires ruby 2.1+' if RUBY_VERSION <= '2.1.0' assert_raises RuntimeError do handler.call(worker, job, 'default') do raise "kerblammo! #{195.chr}" end end assert_equal "kerblammo! �", job["error_message"] end it 'allows a max_retries option in initializer' do max_retries = 7 1.upto(max_retries + 1) do assert_raises RuntimeError do handler(:max_retries => max_retries).call(worker, job, 'default') do raise "kerblammo!" end end end assert_equal max_retries, Sidekiq::RetrySet.new.size assert_equal 1, Sidekiq::DeadSet.new.size end it 'saves backtraces' do c = nil assert_raises RuntimeError do handler.call(worker, job('backtrace' => true), 'default') do c = caller(0); raise "kerblammo!" end end assert job["error_backtrace"] assert_equal c[0], job["error_backtrace"][0] end it 'saves partial backtraces' do c = nil assert_raises RuntimeError do handler.call(worker, job('backtrace' => 3), 'default') do c = caller(0)[0...3]; raise "kerblammo!" end end assert job["error_backtrace"] assert_equal c, job["error_backtrace"] assert_equal 3, c.size end it 'handles a new failed message' do assert_raises RuntimeError do handler.call(worker, job, 'default') do raise "kerblammo!" end end assert_equal 'default', job["queue"] assert_equal 'kerblammo!', job["error_message"] assert_equal 'RuntimeError', job["error_class"] assert_equal 0, job["retry_count"] refute job["error_backtrace"] assert job["failed_at"] end it 'shuts down without retrying work-in-progress, which will resume' do rs = Sidekiq::RetrySet.new assert_equal 0, rs.size msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true } assert_raises Sidekiq::Shutdown do handler.call(worker, msg, 'default') do raise Sidekiq::Shutdown end end assert_equal 0, rs.size end it 'shuts down cleanly when shutdown causes exception' do skip('Not supported in Ruby < 2.1.0') if RUBY_VERSION < '2.1.0' rs = Sidekiq::RetrySet.new assert_equal 0, rs.size msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true } assert_raises Sidekiq::Shutdown do handler.call(worker, msg, 'default') do begin raise Sidekiq::Shutdown rescue Interrupt raise "kerblammo!" end end end assert_equal 0, rs.size end it 'shuts down cleanly when shutdown causes chained exceptions' do skip('Not supported in Ruby < 2.1.0') if RUBY_VERSION < '2.1.0' rs = Sidekiq::RetrySet.new assert_equal 0, rs.size assert_raises Sidekiq::Shutdown do handler.call(worker, job, 'default') do begin raise Sidekiq::Shutdown rescue Interrupt begin raise "kerblammo!" rescue raise "kablooie!" end end end end assert_equal 0, rs.size end it 'allows a retry queue' do assert_raises RuntimeError do handler.call(worker, job("retry_queue" => 'retryx'), 'default') do raise "kerblammo!" end end assert_equal 'retryx', job["queue"] assert_equal 'kerblammo!', job["error_message"] assert_equal 'RuntimeError', job["error_class"] assert_equal 0, job["retry_count"] refute job["error_backtrace"] assert job["failed_at"] end it 'handles a recurring failed message' do now = Time.now.to_f msg = {"queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>10} assert_raises RuntimeError do handler.call(worker, job(msg), 'default') do raise "kerblammo!" end end assert_equal 'default', job["queue"] assert_equal 'kerblammo!', job["error_message"] assert_equal 'RuntimeError', job["error_class"] assert_equal 11, job["retry_count"] assert job["failed_at"] end it 'throws away old messages after too many retries (using the default)' do q = Sidekiq::Queue.new rs = Sidekiq::RetrySet.new ds = Sidekiq::DeadSet.new assert_equal 0, q.size assert_equal 0, rs.size assert_equal 0, ds.size now = Time.now.to_f msg = {"queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>25} assert_raises RuntimeError do handler.call(worker, job(msg), 'default') do raise "kerblammo!" end end assert_equal 0, q.size assert_equal 0, rs.size assert_equal 1, ds.size end describe "custom retry delay" do before do @old_logger = Sidekiq.logger @tmp_log_path = '/tmp/sidekiq-retries.log' Sidekiq.logger = Logger.new(@tmp_log_path) end after do Sidekiq.logger = @old_logger Sidekiq.options.delete(:logfile) File.unlink @tmp_log_path if File.exist?(@tmp_log_path) end class CustomWorkerWithoutException include Sidekiq::Worker sidekiq_retry_in do |count| count * 2 end end class CustomWorkerWithException include Sidekiq::Worker sidekiq_retry_in do |count, exception| case exception when ArgumentError count * 4 else count * 2 end end end class ErrorWorker include Sidekiq::Worker sidekiq_retry_in do |count| count / 0 end end it "retries with a default delay" do refute_equal 4, handler.__send__(:delay_for, worker, 2, StandardError.new) end it "retries with a custom delay and exception 1" do assert_equal 8, handler.__send__(:delay_for, CustomWorkerWithException, 2, ArgumentError.new) end it "retries with a custom delay and exception 2" do assert_equal 4, handler.__send__(:delay_for, CustomWorkerWithException, 2, StandardError.new) end it "retries with a custom delay without exception" do assert_equal 4, handler.__send__(:delay_for, CustomWorkerWithoutException, 2, StandardError.new) end it "falls back to the default retry on exception" do refute_equal 4, handler.__send__(:delay_for, ErrorWorker, 2, StandardError.new) assert_match(/Failure scheduling retry using the defined `sidekiq_retry_in`/, File.read(@tmp_log_path), 'Log entry missing for sidekiq_retry_in') end end describe 'handles errors withouth cause' do before do @error = nil begin raise ::StandardError, 'Error' rescue ::StandardError => e @error = e end end it "does not recurse infinitely checking if it's a shutdown" do assert(!Sidekiq::Middleware::Server::RetryJobs.new.send( :exception_caused_by_shutdown?, @error)) end end describe 'handles errors with circular causes' do before do @error = nil begin begin raise ::StandardError, 'Error 1' rescue ::StandardError => e1 begin raise ::StandardError, 'Error 2' rescue ::StandardError raise e1 end end rescue ::StandardError => e @error = e end end it "does not recurse infinitely checking if it's a shutdown" do assert(!Sidekiq::Middleware::Server::RetryJobs.new.send( :exception_caused_by_shutdown?, @error)) end end end end sidekiq-4.0.1/test/test_logging.rb0000644000175600017570000000144712631157272016213 0ustar pravipravirequire_relative 'helper' require 'sidekiq/logging' class TestLogging < Sidekiq::Test describe Sidekiq::Logging do describe "#with_context" do def context Sidekiq::Logging.logger.formatter.context end it "has no context by default" do context.must_equal nil end it "can add a context" do Sidekiq::Logging.with_context "xx" do context.must_equal " xx" end context.must_equal nil end it "can use multiple contexts" do Sidekiq::Logging.with_context "xx" do context.must_equal " xx" Sidekiq::Logging.with_context "yy" do context.must_equal " xx yy" end context.must_equal " xx" end context.must_equal nil end end end end sidekiq-4.0.1/test/test_web.rb0000644000175600017570000004564112631157272015346 0ustar pravipravi# encoding: utf-8 require_relative 'helper' require 'sidekiq/web' require 'rack/test' require 'tilt/erubis' class TestWeb < Sidekiq::Test describe 'sidekiq web' do include Rack::Test::Methods def app Sidekiq::Web end def job_params(job, score) "#{score}-#{job['jid']}" end before do Sidekiq.redis {|c| c.flushdb } end class WebWorker include Sidekiq::Worker def perform(a, b) a + b end end it 'can show text with any locales' do rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'ru,en'} get '/', {}, rackenv assert_match(/Панель управления/, last_response.body) rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'es,en'} get '/', {}, rackenv assert_match(/Panel de Control/, last_response.body) rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'en-us'} get '/', {}, rackenv assert_match(/Dashboard/, last_response.body) rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-cn'} get '/', {}, rackenv assert_match(/信息板/, last_response.body) rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-tw'} get '/', {}, rackenv assert_match(/資訊主頁/, last_response.body) rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'nb'} get '/', {}, rackenv assert_match(/Oversikt/, last_response.body) end describe 'busy' do it 'can display workers' do Sidekiq.redis do |conn| conn.incr('busy') conn.sadd('processes', 'foo:1234') conn.hmset('foo:1234', 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4) identity = 'foo:1234:workers' hash = {:queue => 'critical', :payload => { 'class' => WebWorker.name, 'args' => [1,'abc'] }, :run_at => Time.now.to_i } conn.hmset(identity, 1001, Sidekiq.dump_json(hash)) end assert_equal ['1001'], Sidekiq::Workers.new.map { |pid, tid, data| tid } get '/busy' assert_equal 200, last_response.status assert_match(/status-active/, last_response.body) assert_match(/critical/, last_response.body) assert_match(/WebWorker/, last_response.body) end it 'can quiet a process' do identity = 'identity' signals_key = "#{identity}-signals" assert_nil Sidekiq.redis { |c| c.lpop signals_key } post '/busy', 'quiet' => '1', 'identity' => identity assert_equal 302, last_response.status assert_equal 'USR1', Sidekiq.redis { |c| c.lpop signals_key } end it 'can stop a process' do identity = 'identity' signals_key = "#{identity}-signals" assert_nil Sidekiq.redis { |c| c.lpop signals_key } post '/busy', 'stop' => '1', 'identity' => identity assert_equal 302, last_response.status assert_equal 'TERM', Sidekiq.redis { |c| c.lpop signals_key } end end it 'can display queues' do assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3]) get '/queues' assert_equal 200, last_response.status assert_match(/foo/, last_response.body) refute_match(/HardWorker/, last_response.body) end it 'handles queue view' do get '/queues/default' assert_equal 200, last_response.status end it 'can delete a queue' do Sidekiq.redis do |conn| conn.rpush('queue:foo', '{}') conn.sadd('queues', 'foo') end get '/queues/foo' assert_equal 200, last_response.status post '/queues/foo' assert_equal 302, last_response.status Sidekiq.redis do |conn| refute conn.smembers('queues').include?('foo') refute conn.exists('queue:foo') end end it 'can delete a job' do Sidekiq.redis do |conn| conn.rpush('queue:foo', "{}") conn.rpush('queue:foo', "{\"foo\":\"bar\"}") conn.rpush('queue:foo', "{\"foo2\":\"bar2\"}") end get '/queues/foo' assert_equal 200, last_response.status post '/queues/foo/delete', key_val: "{\"foo\":\"bar\"}" assert_equal 302, last_response.status Sidekiq.redis do |conn| refute conn.lrange('queue:foo', 0, -1).include?("{\"foo\":\"bar\"}") end end it 'can display retries' do get '/retries' assert_equal 200, last_response.status assert_match(/found/, last_response.body) refute_match(/HardWorker/, last_response.body) add_retry get '/retries' assert_equal 200, last_response.status refute_match(/found/, last_response.body) assert_match(/HardWorker/, last_response.body) end it 'can display a single retry' do params = add_retry get '/retries/0-shouldntexist' assert_equal 302, last_response.status get "/retries/#{job_params(*params)}" assert_equal 200, last_response.status assert_match(/HardWorker/, last_response.body) end it 'handles missing retry' do get "/retries/0-shouldntexist" assert_equal 302, last_response.status end it 'can delete a single retry' do params = add_retry post "/retries/#{job_params(*params)}", 'delete' => 'Delete' assert_equal 302, last_response.status assert_equal 'http://example.org/retries', last_response.header['Location'] get "/retries" assert_equal 200, last_response.status refute_match(/#{params.first['args'][2]}/, last_response.body) end it 'can delete all retries' do 3.times { add_retry } post "/retries/all/delete", 'delete' => 'Delete' assert_equal 0, Sidekiq::RetrySet.new.size assert_equal 302, last_response.status assert_equal 'http://example.org/retries', last_response.header['Location'] end it 'can retry a single retry now' do params = add_retry post "/retries/#{job_params(*params)}", 'retry' => 'Retry' assert_equal 302, last_response.status assert_equal 'http://example.org/retries', last_response.header['Location'] get '/queues/default' assert_equal 200, last_response.status assert_match(/#{params.first['args'][2]}/, last_response.body) end it 'can kill a single retry now' do params = add_retry post "/retries/#{job_params(*params)}", 'kill' => 'Kill' assert_equal 302, last_response.status assert_equal 'http://example.org/retries', last_response.header['Location'] get '/morgue' assert_equal 200, last_response.status assert_match(/#{params.first['args'][2]}/, last_response.body) end it 'can display scheduled' do get '/scheduled' assert_equal 200, last_response.status assert_match(/found/, last_response.body) refute_match(/HardWorker/, last_response.body) add_scheduled get '/scheduled' assert_equal 200, last_response.status refute_match(/found/, last_response.body) assert_match(/HardWorker/, last_response.body) end it 'can display a single scheduled job' do params = add_scheduled get '/scheduled/0-shouldntexist' assert_equal 302, last_response.status get "/scheduled/#{job_params(*params)}" assert_equal 200, last_response.status assert_match(/HardWorker/, last_response.body) end it 'handles missing scheduled job' do get "/scheduled/0-shouldntexist" assert_equal 302, last_response.status end it 'can add to queue a single scheduled job' do params = add_scheduled post "/scheduled/#{job_params(*params)}", 'add_to_queue' => true assert_equal 302, last_response.status assert_equal 'http://example.org/scheduled', last_response.header['Location'] get '/queues/default' assert_equal 200, last_response.status assert_match(/#{params.first['args'][2]}/, last_response.body) end it 'can delete a single scheduled job' do params = add_scheduled post "/scheduled/#{job_params(*params)}", 'delete' => 'Delete' assert_equal 302, last_response.status assert_equal 'http://example.org/scheduled', last_response.header['Location'] get "/scheduled" assert_equal 200, last_response.status refute_match(/#{params.first['args'][2]}/, last_response.body) end it 'can delete scheduled' do params = add_scheduled Sidekiq.redis do |conn| assert_equal 1, conn.zcard('schedule') post '/scheduled', 'key' => [job_params(*params)], 'delete' => 'Delete' assert_equal 302, last_response.status assert_equal 'http://example.org/scheduled', last_response.header['Location'] assert_equal 0, conn.zcard('schedule') end end it "can move scheduled to default queue" do q = Sidekiq::Queue.new params = add_scheduled Sidekiq.redis do |conn| assert_equal 1, conn.zcard('schedule') assert_equal 0, q.size post '/scheduled', 'key' => [job_params(*params)], 'add_to_queue' => 'AddToQueue' assert_equal 302, last_response.status assert_equal 'http://example.org/scheduled', last_response.header['Location'] assert_equal 0, conn.zcard('schedule') assert_equal 1, q.size get '/queues/default' assert_equal 200, last_response.status assert_match(/#{params[0]['args'][2]}/, last_response.body) end end it 'can retry all retries' do msg = add_retry.first add_retry post "/retries/all/retry", 'retry' => 'Retry' assert_equal 302, last_response.status assert_equal 'http://example.org/retries', last_response.header['Location'] assert_equal 2, Sidekiq::Queue.new("default").size get '/queues/default' assert_equal 200, last_response.status assert_match(/#{msg['args'][2]}/, last_response.body) end it 'calls updatePage() once when polling' do get '/busy?poll=true' assert_equal 200, last_response.status assert_equal 1, last_response.body.scan('updatePage(').count end it 'escape job args and error messages' do # on /retries page params = add_xss_retry get '/retries' assert_equal 200, last_response.status assert_match(/FailWorker/, last_response.body) assert last_response.body.include?( "fail message: <a>hello</a>" ) assert !last_response.body.include?( "fail message: hello" ) assert last_response.body.include?( "args\">"<a>hello</a>"<" ) assert !last_response.body.include?( "args\">hello<" ) # on /workers page Sidekiq.redis do |conn| pro = 'foo:1234' conn.sadd('processes', pro) conn.hmset(pro, 'info', Sidekiq.dump_json('started_at' => Time.now.to_f, 'labels' => ['frumduz'], 'queues' =>[]), 'busy', 1, 'beat', Time.now.to_f) identity = "#{pro}:workers" hash = {:queue => 'critical', :payload => { 'class' => "FailWorker", 'args' => ["hello"] }, :run_at => Time.now.to_i } conn.hmset(identity, 100001, Sidekiq.dump_json(hash)) conn.incr('busy') end get '/busy' assert_equal 200, last_response.status assert_match(/FailWorker/, last_response.body) assert_match(/frumduz/, last_response.body) assert last_response.body.include?( "<a>hello</a>" ) assert !last_response.body.include?( "hello" ) # on /queues page params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise. post "/retries/#{job_params(*params)}", 'retry' => 'Retry' assert_equal 302, last_response.status get '/queues/foo' assert_equal 200, last_response.status assert last_response.body.include?( "<a>hello</a>" ) assert !last_response.body.include?( "hello" ) end it 'can show user defined tab' do begin Sidekiq::Web.tabs['Custom Tab'] = '/custom' get '/' assert_match 'Custom Tab', last_response.body ensure Sidekiq::Web.tabs.delete 'Custom Tab' end end it 'can display home' do get '/' assert_equal 200, last_response.status end Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures") it 'can show user defined tab with custom locales' do begin Sidekiq::Web.tabs['Custom Tab'] = '/custom' Sidekiq::Web.get('/custom') do t('translated_text') end get '/custom' assert_match(/Changed text/, last_response.body) ensure Sidekiq::Web.tabs.delete 'Custom Tab' end end describe 'dashboard/stats' do it 'redirects to stats' do get '/dashboard/stats' assert_equal 302, last_response.status assert_equal 'http://example.org/stats', last_response.header['Location'] end end describe 'stats' do include Sidekiq::Util before do Sidekiq.redis do |conn| conn.set("stat:processed", 5) conn.set("stat:failed", 2) conn.sadd("queues", "default") end 2.times { add_retry } 3.times { add_scheduled } 4.times { add_worker } get '/stats' @response = Sidekiq.load_json(last_response.body) end it 'can refresh dashboard stats' do assert_equal 200, last_response.status end describe "for sidekiq" do it 'are namespaced' do assert_includes @response.keys, "sidekiq" end it 'reports processed' do assert_equal 5, @response["sidekiq"]["processed"] end it 'reports failed' do assert_equal 2, @response["sidekiq"]["failed"] end it 'reports busy' do assert_equal 4, @response["sidekiq"]["busy"] end it 'reports processes' do assert_equal 1, @response["sidekiq"]["processes"] end it 'reports retries' do assert_equal 2, @response["sidekiq"]["retries"] end it 'reports scheduled' do assert_equal 3, @response["sidekiq"]["scheduled"] end it 'reports latency' do assert_equal 0, @response["sidekiq"]["default_latency"] end end describe "for redis" do it 'are namespaced' do assert_includes @response.keys, "redis" end it 'reports version' do assert_includes @response["redis"].keys, "redis_version" end it 'reports uptime' do assert_includes @response["redis"].keys, "uptime_in_days" end it 'reports connected clients' do assert_includes @response["redis"].keys, "connected_clients" end it 'reports user memory' do assert_includes @response["redis"].keys, "used_memory_human" end it 'reports memory peak' do assert_includes @response["redis"].keys, "used_memory_peak_human" end end end describe 'stats/queues' do include Sidekiq::Util before do Sidekiq.redis do |conn| conn.set("stat:processed", 5) conn.set("stat:failed", 2) conn.sadd("queues", "default") conn.sadd("queues", "queue2") end 2.times { add_retry } 3.times { add_scheduled } 4.times { add_worker } get '/stats/queues' @response = Sidekiq.load_json(last_response.body) end it 'reports the queue depth' do assert_equal 0, @response["default"] assert_equal 0, @response["queue2"] end end describe 'dead jobs' do it 'shows empty index' do get 'morgue' assert_equal 200, last_response.status end it 'shows index with jobs' do (_, score) = add_dead get 'morgue' assert_equal 200, last_response.status assert_match(/#{score}/, last_response.body) end it 'can delete all dead' do 3.times { add_dead } assert_equal 3, Sidekiq::DeadSet.new.size post "/morgue/all/delete", 'delete' => 'Delete' assert_equal 0, Sidekiq::DeadSet.new.size assert_equal 302, last_response.status assert_equal 'http://example.org/morgue', last_response.header['Location'] end it 'can retry a dead job' do params = add_dead post "/morgue/#{job_params(*params)}", 'retry' => 'Retry' assert_equal 302, last_response.status assert_equal 'http://example.org/morgue', last_response.header['Location'] get '/queues/foo' assert_equal 200, last_response.status assert_match(/#{params.first['args'][2]}/, last_response.body) end end def add_scheduled score = Time.now.to_f msg = { 'class' => 'HardWorker', 'args' => ['bob', 1, Time.now.to_f], 'jid' => SecureRandom.hex(12) } Sidekiq.redis do |conn| conn.zadd('schedule', score, Sidekiq.dump_json(msg)) end [msg, score] end def add_retry msg = { 'class' => 'HardWorker', 'args' => ['bob', 1, Time.now.to_f], 'queue' => 'default', 'error_message' => 'Some fake message', 'error_class' => 'RuntimeError', 'retry_count' => 0, 'failed_at' => Time.now.to_f, 'jid' => SecureRandom.hex(12) } score = Time.now.to_f Sidekiq.redis do |conn| conn.zadd('retry', score, Sidekiq.dump_json(msg)) end [msg, score] end def add_dead msg = { 'class' => 'HardWorker', 'args' => ['bob', 1, Time.now.to_f], 'queue' => 'foo', 'error_message' => 'Some fake message', 'error_class' => 'RuntimeError', 'retry_count' => 0, 'failed_at' => Time.now.utc, 'jid' => SecureRandom.hex(12) } score = Time.now.to_f Sidekiq.redis do |conn| conn.zadd('dead', score, Sidekiq.dump_json(msg)) end [msg, score] end def add_xss_retry msg = { 'class' => 'FailWorker', 'args' => ['hello'], 'queue' => 'foo', 'error_message' => 'fail message: hello', 'error_class' => 'RuntimeError', 'retry_count' => 0, 'failed_at' => Time.now.to_f, 'jid' => SecureRandom.hex(12) } score = Time.now.to_f Sidekiq.redis do |conn| conn.zadd('retry', score, Sidekiq.dump_json(msg)) end [msg, score] end def add_worker key = "#{hostname}:#{$$}" msg = "{\"queue\":\"default\",\"payload\":{\"retry\":true,\"queue\":\"default\",\"timeout\":20,\"backtrace\":5,\"class\":\"HardWorker\",\"args\":[\"bob\",10,5],\"jid\":\"2b5ad2b016f5e063a1c62872\"},\"run_at\":1361208995}" Sidekiq.redis do |conn| conn.multi do conn.sadd("processes", key) conn.hmset(key, 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4) conn.hmset("#{key}:workers", Time.now.to_f, msg) end end end end end sidekiq-4.0.1/test/helper.rb0000644000175600017570000000265312631157272015005 0ustar pravipravi$TESTING = true # disable minitest/parallel threads ENV["N"] = "0" if ENV["COVERAGE"] require 'simplecov' SimpleCov.start do add_filter "/test/" add_filter "/myapp/" end end ENV['RACK_ENV'] = ENV['RAILS_ENV'] = 'test' trap 'USR1' do threads = Thread.list puts puts "=" * 80 puts "Received USR1 signal; printing all #{threads.count} thread backtraces." threads.each do |thr| description = thr == Thread.main ? "Main thread" : thr.inspect puts puts "#{description} backtrace: " puts thr.backtrace.join("\n") end puts "=" * 80 end begin require 'pry-byebug' rescue LoadError end require 'minitest/autorun' require 'sidekiq' require 'sidekiq/util' Sidekiq.logger.level = Logger::ERROR Sidekiq::Test = Minitest::Test require 'sidekiq/redis_connection' REDIS_URL = ENV['REDIS_URL'] || 'redis://localhost/15' REDIS = Sidekiq::RedisConnection.create(:url => REDIS_URL, :namespace => 'testy') Sidekiq.configure_client do |config| config.redis = { :url => REDIS_URL, :namespace => 'testy' } end def capture_logging(lvl=Logger::INFO) old = Sidekiq.logger begin out = StringIO.new logger = Logger.new(out) logger.level = lvl Sidekiq.logger = logger yield out.string ensure Sidekiq.logger = old end end def with_logging(lvl=Logger::DEBUG) old = Sidekiq.logger.level begin Sidekiq.logger.level = lvl yield ensure Sidekiq.logger.level = old end end sidekiq-4.0.1/test/test_manager.rb0000644000175600017570000000217412631157272016175 0ustar pravipravirequire_relative 'helper' require 'sidekiq/manager' class TestManager < Sidekiq::Test describe 'manager' do before do Sidekiq.redis {|c| c.flushdb } end def new_manager(opts) Sidekiq::Manager.new(opts) end it 'creates N processor instances' do mgr = new_manager(options) assert_equal options[:concurrency], mgr.workers.size end it 'shuts down the system' do mgr = new_manager(options) mgr.stop(Time.now) end it 'throws away dead processors' do mgr = new_manager(options) init_size = mgr.workers.size processor = mgr.workers.first begin mgr.processor_died(processor, 'ignored') assert_equal init_size, mgr.workers.size refute mgr.workers.include?(processor) ensure mgr.workers.each {|p| p.terminate(true) } end end it 'does not support invalid concurrency' do assert_raises(ArgumentError) { new_manager(concurrency: 0) } assert_raises(ArgumentError) { new_manager(concurrency: -1) } end def options { :concurrency => 3, :queues => ['default'] } end end end sidekiq-4.0.1/test/test_util.rb0000644000175600017570000000046112631157272015535 0ustar pravipravirequire_relative 'helper' class TestUtil < Sidekiq::Test class Helpers include Sidekiq::Util end def test_hertz_donut obj = Helpers.new output = capture_logging(Logger::DEBUG) do assert_equal false, obj.want_a_hertz_donut? end assert_includes output, "hz: 10" end end sidekiq-4.0.1/test/test_scheduled.rb0000644000175600017570000001007512631157272016522 0ustar pravipravirequire_relative 'helper' require 'sidekiq/scheduled' class TestScheduled < Sidekiq::Test class ScheduledWorker include Sidekiq::Worker def perform(x) end end describe 'poller' do before do Sidekiq.redis{|c| c.flushdb} @error_1 = { 'class' => ScheduledWorker.name, 'args' => [0], 'queue' => 'queue_1' } @error_2 = { 'class' => ScheduledWorker.name, 'args' => [1], 'queue' => 'queue_2' } @error_3 = { 'class' => ScheduledWorker.name, 'args' => [2], 'queue' => 'queue_3' } @future_1 = { 'class' => ScheduledWorker.name, 'args' => [3], 'queue' => 'queue_4' } @future_2 = { 'class' => ScheduledWorker.name, 'args' => [4], 'queue' => 'queue_5' } @future_3 = { 'class' => ScheduledWorker.name, 'args' => [5], 'queue' => 'queue_6' } @retry = Sidekiq::RetrySet.new @scheduled = Sidekiq::ScheduledSet.new @poller = Sidekiq::Scheduled::Poller.new end class Stopper def call(worker_class, job, queue, r) yield if job['args'].first.odd? end end it 'executes client middleware' do Sidekiq.client_middleware.add Stopper begin @retry.schedule (Time.now - 60).to_f, @error_1 @retry.schedule (Time.now - 60).to_f, @error_2 @scheduled.schedule (Time.now - 60).to_f, @future_2 @scheduled.schedule (Time.now - 60).to_f, @future_3 @poller.enqueue assert_equal 0, Sidekiq::Queue.new("queue_1").size assert_equal 1, Sidekiq::Queue.new("queue_2").size assert_equal 0, Sidekiq::Queue.new("queue_5").size assert_equal 1, Sidekiq::Queue.new("queue_6").size ensure Sidekiq.client_middleware.remove Stopper end end it 'should empty the retry and scheduled queues up to the current time' do created_time = Time.new(2013, 2, 3) enqueued_time = Time.new(2013, 2, 4) Time.stub(:now, created_time) do @retry.schedule (enqueued_time - 60).to_f, @error_1.merge!('created_at' => created_time.to_f) @retry.schedule (enqueued_time - 50).to_f, @error_2.merge!('created_at' => created_time.to_f) @retry.schedule (enqueued_time + 60).to_f, @error_3.merge!('created_at' => created_time.to_f) @scheduled.schedule (enqueued_time - 60).to_f, @future_1.merge!('created_at' => created_time.to_f) @scheduled.schedule (enqueued_time - 50).to_f, @future_2.merge!('created_at' => created_time.to_f) @scheduled.schedule (enqueued_time + 60).to_f, @future_3.merge!('created_at' => created_time.to_f) end Time.stub(:now, enqueued_time) do @poller.enqueue Sidekiq.redis do |conn| %w(queue:queue_1 queue:queue_2 queue:queue_4 queue:queue_5).each do |queue_name| assert_equal 1, conn.llen(queue_name) job = Sidekiq.load_json(conn.lrange(queue_name, 0, -1)[0]) assert_equal enqueued_time.to_f, job['enqueued_at'] assert_equal created_time.to_f, job['created_at'] end end assert_equal 1, @retry.size assert_equal 1, @scheduled.size end end def with_sidekiq_option(name, value) _original, Sidekiq.options[name] = Sidekiq.options[name], value begin yield ensure Sidekiq.options[name] = _original end end it 'generates random intervals that target a configured average' do with_sidekiq_option(:poll_interval_average, 10) do i = 500 intervals = i.times.map{ @poller.send(:random_poll_interval) } assert intervals.all?{|x| x >= 5} assert intervals.all?{|x| x <= 15} assert_in_delta 10, intervals.reduce(&:+).to_f / i, 0.5 end end it 'calculates an average poll interval based on the number of known Sidekiq processes' do with_sidekiq_option(:average_scheduled_poll_interval, 10) do 3.times do |i| Sidekiq.redis do |conn| conn.sadd("processes", "process-#{i}") conn.hset("process-#{i}", "info", nil) end end assert_equal 30, @poller.send(:scaled_poll_interval) end end end end sidekiq-4.0.1/test/fake_env.rb0000644000175600017570000000000012631157272015264 0ustar pravipravisidekiq-4.0.1/test/test_rails.rb0000644000175600017570000000067612631157272015702 0ustar pravipravirequire_relative 'helper' $HAS_AJ = true begin require 'active_job' rescue LoadError $HAS_AJ = false end class TestRails < Sidekiq::Test describe 'ActiveJob' do it 'does not allow Sidekiq::Worker in AJ::Base classes' do ex = assert_raises ArgumentError do c = Class.new(ActiveJob::Base) c.send(:include, Sidekiq::Worker) end assert_includes ex.message, "cannot include" end if $HAS_AJ end end sidekiq-4.0.1/test/env_based_config.yml0000644000175600017570000000036512631157272017175 0ustar pravipravi--- :pidfile: /tmp/sidekiq-config-test.pid :concurrency: 50 staging: :verbose: false :require: ./test/fake_env.rb :logfile: /tmp/sidekiq.log :concurrency: 5 :queues: - [<%="very_"%>often, 2] - [seldom, 1] sidekiq-4.0.1/test/test_sidekiq.rb0000644000175600017570000000543212631157272016214 0ustar pravipravi# encoding: utf-8 require_relative 'helper' class TestSidekiq < Sidekiq::Test describe 'json processing' do it 'handles json' do assert_equal({"foo" => "bar"}, Sidekiq.load_json("{\"foo\":\"bar\"}")) assert_equal "{\"foo\":\"bar\"}", Sidekiq.dump_json({ "foo" => "bar" }) end end describe "redis connection" do it "returns error without creating a connection if block is not given" do assert_raises(ArgumentError) do Sidekiq.redis end end end describe "❨╯°□°❩╯︵┻━┻" do before { $stdout = StringIO.new } after { $stdout = STDOUT } it "allows angry developers to express their emotional constitution and remedies it" do Sidekiq.❨╯°□°❩╯︵┻━┻ assert_equal "Calm down, yo.\n", $stdout.string end end describe 'lifecycle events' do it 'handles invalid input' do Sidekiq.options[:lifecycle_events][:startup].clear e = assert_raises ArgumentError do Sidekiq.on(:startp) end assert_match(/Invalid event name/, e.message) e = assert_raises ArgumentError do Sidekiq.on('startup') end assert_match(/Symbols only/, e.message) Sidekiq.on(:startup) do 1 + 1 end assert_equal 2, Sidekiq.options[:lifecycle_events][:startup].first.call end end describe 'default_worker_options' do it 'stringifies keys' do @old_options = Sidekiq.default_worker_options begin Sidekiq.default_worker_options = { queue: 'cat'} assert_equal 'cat', Sidekiq.default_worker_options['queue'] ensure Sidekiq.default_worker_options = @old_options end end end describe 'error handling' do it 'deals with user-specified error handlers which raise errors' do output = capture_logging do begin Sidekiq.error_handlers << proc {|x, hash| raise 'boom' } cli = Sidekiq::CLI.new cli.handle_exception(RuntimeError.new("hello")) ensure Sidekiq.error_handlers.pop end end assert_includes output, "boom" assert_includes output, "ERROR" end end describe 'redis connection' do it 'does not continually retry' do assert_raises Redis::CommandError do Sidekiq.redis do |c| raise Redis::CommandError, "READONLY You can't write against a read only slave." end end end it 'reconnects if connection is flagged as readonly' do counts = [] Sidekiq.redis do |c| counts << c.info['total_connections_received'].to_i raise Redis::CommandError, "READONLY You can't write against a read only slave." if counts.size == 1 end assert_equal 2, counts.size assert_equal counts[0] + 1, counts[1] end end end sidekiq-4.0.1/test/test_middleware.rb0000644000175600017570000001060112631157272016672 0ustar pravipravirequire_relative 'helper' require 'sidekiq/middleware/chain' require 'sidekiq/processor' class TestMiddleware < Sidekiq::Test describe 'middleware chain' do before do $errors = [] Sidekiq.redis = REDIS end class CustomMiddleware def initialize(name, recorder) @name = name @recorder = recorder end def call(*args) @recorder << [@name, 'before'] yield @recorder << [@name, 'after'] end end it 'supports custom middleware' do chain = Sidekiq::Middleware::Chain.new chain.add CustomMiddleware, 1, [] assert_equal CustomMiddleware, chain.entries.last.klass end class CustomWorker $recorder = [] include Sidekiq::Worker def perform(recorder) $recorder << ['work_performed'] end end class NonYieldingMiddleware def call(*args) end end class AnotherCustomMiddleware def initialize(name, recorder) @name = name @recorder = recorder end def call(*args) @recorder << [@name, 'before'] yield @recorder << [@name, 'after'] end end class YetAnotherCustomMiddleware def initialize(name, recorder) @name = name @recorder = recorder end def call(*args) @recorder << [@name, 'before'] yield @recorder << [@name, 'after'] end end it 'executes middleware in the proper order' do msg = Sidekiq.dump_json({ 'class' => CustomWorker.to_s, 'args' => [$recorder] }) Sidekiq.server_middleware do |chain| # should only add once, second should replace the first 2.times { |i| chain.add CustomMiddleware, i.to_s, $recorder } chain.insert_before CustomMiddleware, AnotherCustomMiddleware, '2', $recorder chain.insert_after AnotherCustomMiddleware, YetAnotherCustomMiddleware, '3', $recorder end boss = Minitest::Mock.new boss.expect(:options, {:queues => ['default'] }, []) boss.expect(:options, {:queues => ['default'] }, []) processor = Sidekiq::Processor.new(boss) boss.expect(:processor_done, nil, [processor]) processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg)) assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten end it 'correctly replaces middleware when using middleware with options in the initializer' do chain = Sidekiq::Middleware::Chain.new chain.add Sidekiq::Middleware::Server::RetryJobs chain.add Sidekiq::Middleware::Server::RetryJobs, {:max_retries => 5} assert_equal 1, chain.count end it 'correctly prepends middleware' do chain = Sidekiq::Middleware::Chain.new chain_entries = chain.entries chain.add CustomMiddleware chain.prepend YetAnotherCustomMiddleware assert_equal YetAnotherCustomMiddleware, chain_entries.first.klass assert_equal CustomMiddleware, chain_entries.last.klass end it 'allows middleware to abruptly stop processing rest of chain' do recorder = [] chain = Sidekiq::Middleware::Chain.new chain.add NonYieldingMiddleware chain.add CustomMiddleware, 1, recorder final_action = nil chain.invoke { final_action = true } assert_equal nil, final_action assert_equal [], recorder end end describe 'i18n' do before do require 'i18n' I18n.enforce_available_locales = false require 'sidekiq/middleware/i18n' end it 'saves and restores locale' do I18n.locale = 'fr' msg = {} mw = Sidekiq::Middleware::I18n::Client.new mw.call(nil, msg, nil, nil) { } assert_equal :fr, msg['locale'] msg['locale'] = 'jp' I18n.locale = I18n.default_locale assert_equal :en, I18n.locale mw = Sidekiq::Middleware::I18n::Server.new mw.call(nil, msg, nil) do assert_equal :jp, I18n.locale end assert_equal :en, I18n.locale end it 'supports I18n.enforce_available_locales = true' do I18n.enforce_available_locales = true I18n.available_locales = [:en, :jp] msg = { 'locale' => 'jp' } mw = Sidekiq::Middleware::I18n::Server.new mw.call(nil, msg, nil) do assert_equal :jp, I18n.locale end I18n.enforce_available_locales = false I18n.available_locales = nil end end end sidekiq-4.0.1/test/test_actors.rb0000644000175600017570000000560512631157272016060 0ustar pravipravirequire_relative 'helper' require 'sidekiq/cli' require 'sidekiq/fetch' require 'sidekiq/scheduled' require 'sidekiq/processor' class TestActors < Sidekiq::Test class JoeWorker include Sidekiq::Worker def perform(slp) raise "boom" if slp == "boom" sleep(slp) if slp > 0 $count += 1 end end describe 'threads' do before do Sidekiq.redis {|c| c.flushdb} end describe 'scheduler' do it 'can start and stop' do f = Sidekiq::Scheduled::Poller.new f.start f.terminate end it 'can schedule' do ss = Sidekiq::ScheduledSet.new q = Sidekiq::Queue.new JoeWorker.perform_in(0.01, 0) assert_equal 0, q.size assert_equal 1, ss.size sleep 0.015 s = Sidekiq::Scheduled::Poller.new s.enqueue assert_equal 1, q.size assert_equal 0, ss.size s.terminate end end describe 'processor' do before do $count = 0 end it 'can start and stop' do f = Sidekiq::Processor.new(Mgr.new) f.terminate end class Mgr attr_reader :latest_error attr_reader :mutex attr_reader :cond def initialize @mutex = ::Mutex.new @cond = ::ConditionVariable.new end def processor_died(inst, err) @latest_error = err @mutex.synchronize do @cond.signal end end def processor_stopped(inst) @mutex.synchronize do @cond.signal end end def options { :concurrency => 3, :queues => ['default'] } end end it 'can process' do mgr = Mgr.new p = Sidekiq::Processor.new(mgr) JoeWorker.perform_async(0) a = $count p.process_one b = $count assert_equal a + 1, b end it 'deals with errors' do mgr = Mgr.new p = Sidekiq::Processor.new(mgr) JoeWorker.perform_async("boom") q = Sidekiq::Queue.new assert_equal 1, q.size a = $count mgr.mutex.synchronize do p.start mgr.cond.wait(mgr.mutex) end b = $count assert_equal a, b sleep 0.001 assert_equal false, p.thread.status p.terminate(true) refute_nil mgr.latest_error assert_equal RuntimeError, mgr.latest_error.class end it 'gracefully kills' do mgr = Mgr.new p = Sidekiq::Processor.new(mgr) JoeWorker.perform_async(1) q = Sidekiq::Queue.new assert_equal 1, q.size a = $count p.start sleep(0.02) p.terminate p.kill(true) b = $count assert_equal a, b assert_equal false, p.thread.status refute mgr.latest_error, mgr.latest_error.to_s end end end end sidekiq-4.0.1/test/test_testing.rb0000644000175600017570000000643012631157272016237 0ustar pravipravirequire_relative 'helper' require 'active_record' require 'action_mailer' require 'sidekiq/rails' require 'sidekiq/extensions/action_mailer' require 'sidekiq/extensions/active_record' Sidekiq.hook_rails! class TestTesting < Sidekiq::Test describe 'sidekiq testing' do describe 'require/load sidekiq/testing.rb' do before do require 'sidekiq/testing' end after do Sidekiq::Testing.disable! end it 'enables fake testing' do Sidekiq::Testing.fake! assert Sidekiq::Testing.enabled? assert Sidekiq::Testing.fake? refute Sidekiq::Testing.inline? end it 'enables fake testing in a block' do Sidekiq::Testing.disable! assert Sidekiq::Testing.disabled? refute Sidekiq::Testing.fake? Sidekiq::Testing.fake! do assert Sidekiq::Testing.enabled? assert Sidekiq::Testing.fake? refute Sidekiq::Testing.inline? end refute Sidekiq::Testing.enabled? refute Sidekiq::Testing.fake? end it 'disables testing in a block' do Sidekiq::Testing.fake! assert Sidekiq::Testing.fake? Sidekiq::Testing.disable! do refute Sidekiq::Testing.fake? assert Sidekiq::Testing.disabled? end assert Sidekiq::Testing.fake? assert Sidekiq::Testing.enabled? end end describe 'require/load sidekiq/testing/inline.rb' do before do require 'sidekiq/testing/inline' end after do Sidekiq::Testing.disable! end it 'enables inline testing' do Sidekiq::Testing.inline! assert Sidekiq::Testing.enabled? assert Sidekiq::Testing.inline? refute Sidekiq::Testing.fake? end it 'enables inline testing in a block' do Sidekiq::Testing.disable! assert Sidekiq::Testing.disabled? refute Sidekiq::Testing.fake? Sidekiq::Testing.inline! do assert Sidekiq::Testing.enabled? assert Sidekiq::Testing.inline? end refute Sidekiq::Testing.enabled? refute Sidekiq::Testing.inline? refute Sidekiq::Testing.fake? end end end describe 'with middleware' do before do require 'sidekiq/testing' end after do Sidekiq::Testing.disable! end class AttributeWorker include Sidekiq::Worker class_attribute :count self.count = 0 attr_accessor :foo def perform self.class.count += 1 if foo == :bar end end class AttributeMiddleware def call(worker, msg, queue) worker.foo = :bar if worker.respond_to?(:foo=) yield end end it 'wraps the inlined worker with middleware' do Sidekiq::Testing.server_middleware do |chain| chain.add AttributeMiddleware end begin Sidekiq::Testing.fake! do AttributeWorker.perform_async assert_equal 0, AttributeWorker.count end AttributeWorker.perform_one assert_equal 1, AttributeWorker.count Sidekiq::Testing.inline! do AttributeWorker.perform_async assert_equal 2, AttributeWorker.count end ensure Sidekiq::Testing.server_middleware.clear end end end end sidekiq-4.0.1/test/test_web_helpers.rb0000644000175600017570000000202212631157272017052 0ustar pravipravirequire_relative 'helper' class TestWebHelpers < Sidekiq::Test class Helpers include Sidekiq::WebHelpers def initialize(params={}) @thehash = default.merge(params) end def request self end def settings self end def locales ['web/locales'] end def env @thehash end def default { } end end def test_locale_determination obj = Helpers.new assert_equal 'en', obj.locale obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2') assert_equal 'fr', obj.locale obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2') assert_equal 'zh-cn', obj.locale obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'nb-NO,nb;q=0.2') assert_equal 'nb', obj.locale obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'en-us; *') assert_equal 'en', obj.locale obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => '*') assert_equal 'en', obj.locale end end sidekiq-4.0.1/test/test_api.rb0000644000175600017570000003636512631157272015345 0ustar pravipravirequire_relative 'helper' require 'sidekiq/api' class TestApi < Sidekiq::Test describe 'api' do before do Sidekiq.redis {|c| c.flushdb } end describe "stats" do it "is initially zero" do s = Sidekiq::Stats.new assert_equal 0, s.processed assert_equal 0, s.failed assert_equal 0, s.enqueued end describe "processed" do it "returns number of processed jobs" do Sidekiq.redis { |conn| conn.set("stat:processed", 5) } s = Sidekiq::Stats.new assert_equal 5, s.processed end end describe "failed" do it "returns number of failed jobs" do Sidekiq.redis { |conn| conn.set("stat:failed", 5) } s = Sidekiq::Stats.new assert_equal 5, s.failed end end describe "reset" do before do Sidekiq.redis do |conn| conn.set('stat:processed', 5) conn.set('stat:failed', 10) end end it 'will reset all stats by default' do Sidekiq::Stats.new.reset s = Sidekiq::Stats.new assert_equal 0, s.failed assert_equal 0, s.processed end it 'can reset individual stats' do Sidekiq::Stats.new.reset('failed') s = Sidekiq::Stats.new assert_equal 0, s.failed assert_equal 5, s.processed end it 'can accept anything that responds to #to_s' do Sidekiq::Stats.new.reset(:failed) s = Sidekiq::Stats.new assert_equal 0, s.failed assert_equal 5, s.processed end it 'ignores anything other than "failed" or "processed"' do Sidekiq::Stats.new.reset((1..10).to_a, ['failed']) s = Sidekiq::Stats.new assert_equal 0, s.failed assert_equal 5, s.processed end end describe "queues" do it "is initially empty" do s = Sidekiq::Stats::Queues.new assert_equal 0, s.lengths.size end it "returns a hash of queue and size in order" do Sidekiq.redis do |conn| conn.rpush 'queue:foo', '{}' conn.sadd 'queues', 'foo' 3.times { conn.rpush 'queue:bar', '{}' } conn.sadd 'queues', 'bar' end s = Sidekiq::Stats::Queues.new assert_equal ({ "foo" => 1, "bar" => 3 }), s.lengths assert_equal "bar", s.lengths.first.first assert_equal Sidekiq::Stats.new.queues, Sidekiq::Stats::Queues.new.lengths end end describe "enqueued" do it "returns total enqueued jobs" do Sidekiq.redis do |conn| conn.rpush 'queue:foo', '{}' conn.sadd 'queues', 'foo' 3.times { conn.rpush 'queue:bar', '{}' } conn.sadd 'queues', 'bar' end s = Sidekiq::Stats.new assert_equal 4, s.enqueued end end describe "over time" do before do require 'active_support/core_ext/time/conversions' @before = Time::DATE_FORMATS[:default] Time::DATE_FORMATS[:default] = "%d/%m/%Y %H:%M:%S" end after do Time::DATE_FORMATS[:default] = @before end describe "processed" do it 'retrieves hash of dates' do Sidekiq.redis do |c| c.incrby("stat:processed:2012-12-24", 4) c.incrby("stat:processed:2012-12-25", 1) c.incrby("stat:processed:2012-12-26", 6) c.incrby("stat:processed:2012-12-27", 2) end Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do s = Sidekiq::Stats::History.new(2) assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1 }, s.processed) s = Sidekiq::Stats::History.new(3) assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) assert_equal({ "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) end end end describe "failed" do it 'retrieves hash of dates' do Sidekiq.redis do |c| c.incrby("stat:failed:2012-12-24", 4) c.incrby("stat:failed:2012-12-25", 1) c.incrby("stat:failed:2012-12-26", 6) c.incrby("stat:failed:2012-12-27", 2) end Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do s = Sidekiq::Stats::History.new(2) assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.failed s = Sidekiq::Stats::History.new(3) assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed end end end end end describe 'with an empty database' do it 'shows queue as empty' do q = Sidekiq::Queue.new assert_equal 0, q.size assert_equal 0, q.latency end class ApiWorker include Sidekiq::Worker end it 'can enumerate jobs' do q = Sidekiq::Queue.new Time.stub(:now, Time.new(2012, 12, 26)) do ApiWorker.perform_async(1, 'mike') assert_equal ['TestApi::ApiWorker'], q.map(&:klass) job = q.first assert_equal 24, job.jid.size assert_equal [1, 'mike'], job.args assert_equal Time.new(2012, 12, 26), job.enqueued_at end assert q.latency > 10_000_000 q = Sidekiq::Queue.new('other') assert_equal 0, q.size end it 'has no enqueued_at time for jobs enqueued in the future' do job_id = ApiWorker.perform_in(100, 1, 'foo') job = Sidekiq::ScheduledSet.new.find_job(job_id) assert_nil job.enqueued_at end it 'unwraps delayed jobs' do Sidekiq::Queue.delay.foo(1,2,3) q = Sidekiq::Queue.new x = q.first assert_equal "Sidekiq::Queue.foo", x.display_class assert_equal [1,2,3], x.display_args end it 'has no enqueued_at time for jobs enqueued in the future' do job_id = ApiWorker.perform_in(100, 1, 'foo') job = Sidekiq::ScheduledSet.new.find_job(job_id) assert_nil job.enqueued_at end it 'can delete jobs' do q = Sidekiq::Queue.new ApiWorker.perform_async(1, 'mike') assert_equal 1, q.size x = q.first assert_equal "TestApi::ApiWorker", x.display_class assert_equal [1,'mike'], x.display_args assert_equal [true], q.map(&:delete) assert_equal 0, q.size end it "can move scheduled job to queue" do remain_id = ApiWorker.perform_in(100, 1, 'jason') job_id = ApiWorker.perform_in(100, 1, 'jason') job = Sidekiq::ScheduledSet.new.find_job(job_id) q = Sidekiq::Queue.new job.add_to_queue queued_job = q.find_job(job_id) refute_nil queued_job assert_equal queued_job.jid, job_id assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) end it "handles multiple scheduled jobs when moving to queue" do jids = Sidekiq::Client.push_bulk('class' => ApiWorker, 'args' => [[1, 'jason'], [2, 'jason']], 'at' => Time.now.to_f) assert_equal 2, jids.size (remain_id, job_id) = jids job = Sidekiq::ScheduledSet.new.find_job(job_id) q = Sidekiq::Queue.new job.add_to_queue queued_job = q.find_job(job_id) refute_nil queued_job assert_equal queued_job.jid, job_id assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) end it 'can find job by id in sorted sets' do job_id = ApiWorker.perform_in(100, 1, 'jason') job = Sidekiq::ScheduledSet.new.find_job(job_id) refute_nil job assert_equal job_id, job.jid assert_in_delta job.latency, 0.0, 0.1 end it 'can remove jobs when iterating over a sorted set' do # scheduled jobs must be greater than SortedSet#each underlying page size 51.times do ApiWorker.perform_in(100, 'aaron') end set = Sidekiq::ScheduledSet.new set.map(&:delete) assert_equal set.size, 0 end it 'can remove jobs when iterating over a queue' do # initial queue size must be greater than Queue#each underlying page size 51.times do ApiWorker.perform_async(1, 'aaron') end q = Sidekiq::Queue.new q.map(&:delete) assert_equal q.size, 0 end it 'can find job by id in queues' do q = Sidekiq::Queue.new job_id = ApiWorker.perform_async(1, 'jason') job = q.find_job(job_id) refute_nil job assert_equal job_id, job.jid end it 'can clear a queue' do q = Sidekiq::Queue.new 2.times { ApiWorker.perform_async(1, 'mike') } q.clear Sidekiq.redis do |conn| refute conn.smembers('queues').include?('foo') refute conn.exists('queue:foo') end end it 'can fetch by score' do same_time = Time.now.to_f add_retry('bob1', same_time) add_retry('bob2', same_time) r = Sidekiq::RetrySet.new assert_equal 2, r.fetch(same_time).size end it 'can fetch by score and jid' do same_time = Time.now.to_f add_retry('bob1', same_time) add_retry('bob2', same_time) r = Sidekiq::RetrySet.new assert_equal 1, r.fetch(same_time, 'bob1').size end it 'shows empty retries' do r = Sidekiq::RetrySet.new assert_equal 0, r.size end it 'can enumerate retries' do add_retry r = Sidekiq::RetrySet.new assert_equal 1, r.size array = r.to_a assert_equal 1, array.size retri = array.first assert_equal 'ApiWorker', retri.klass assert_equal 'default', retri.queue assert_equal 'bob', retri.jid assert_in_delta Time.now.to_f, retri.at.to_f, 0.02 end it 'requires a jid to delete an entry' do start_time = Time.now.to_f add_retry('bob2', Time.now.to_f) assert_raises(ArgumentError) do Sidekiq::RetrySet.new.delete(start_time) end end it 'can delete a single retry from score and jid' do same_time = Time.now.to_f add_retry('bob1', same_time) add_retry('bob2', same_time) r = Sidekiq::RetrySet.new assert_equal 2, r.size Sidekiq::RetrySet.new.delete(same_time, 'bob1') assert_equal 1, r.size end it 'can retry a retry' do add_retry r = Sidekiq::RetrySet.new assert_equal 1, r.size r.first.retry assert_equal 0, r.size assert_equal 1, Sidekiq::Queue.new('default').size job = Sidekiq::Queue.new('default').first assert_equal 'bob', job.jid assert_equal 1, job['retry_count'] end it 'can clear retries' do add_retry add_retry('test') r = Sidekiq::RetrySet.new assert_equal 2, r.size r.clear assert_equal 0, r.size end it 'can enumerate processes' do identity_string = "identity_string" odata = { 'pid' => 123, 'hostname' => Socket.gethostname, 'key' => identity_string, 'identity' => identity_string, 'started_at' => Time.now.to_f - 15, } time = Time.now.to_f Sidekiq.redis do |conn| conn.multi do conn.sadd('processes', odata['key']) conn.hmset(odata['key'], 'info', Sidekiq.dump_json(odata), 'busy', 10, 'beat', time) conn.sadd('processes', 'fake:pid') end end ps = Sidekiq::ProcessSet.new.to_a assert_equal 1, ps.size data = ps.first assert_equal 10, data['busy'] assert_equal time, data['beat'] assert_equal 123, data['pid'] data.quiet! data.stop! signals_string = "#{odata['key']}-signals" assert_equal "TERM", Sidekiq.redis{|c| c.lpop(signals_string) } assert_equal "USR1", Sidekiq.redis{|c| c.lpop(signals_string) } end it 'can enumerate workers' do w = Sidekiq::Workers.new assert_equal 0, w.size w.each do assert false end hn = Socket.gethostname key = "#{hn}:#{$$}" pdata = { 'pid' => $$, 'hostname' => hn, 'started_at' => Time.now.to_i } Sidekiq.redis do |conn| conn.sadd('processes', key) conn.hmset(key, 'info', Sidekiq.dump_json(pdata), 'busy', 0, 'beat', Time.now.to_f) end s = "#{key}:workers" data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => Time.now.to_i }) Sidekiq.redis do |c| c.hmset(s, '1234', data) end w.each do |p, x, y| assert_equal key, p assert_equal "1234", x assert_equal 'default', y['queue'] assert_equal Time.now.year, Time.at(y['run_at']).year end s = "#{key}:workers" data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => (Time.now.to_i - 2*60*60) }) Sidekiq.redis do |c| c.multi do c.hmset(s, '5678', data) c.hmset("b#{s}", '5678', data) end end assert_equal ['1234', '5678'], w.map { |_, tid, _| tid } end it 'can reschedule jobs' do add_retry('foo1') add_retry('foo2') retries = Sidekiq::RetrySet.new assert_equal 2, retries.size refute(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) retries.each do |retri| retri.reschedule(Time.now.to_f + 10) if retri.jid == 'foo2' end assert_equal 2, retries.size assert(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) end it 'prunes processes which have died' do data = { 'pid' => rand(10_000), 'hostname' => "app#{rand(1_000)}", 'started_at' => Time.now.to_f } key = "#{data['hostname']}:#{data['pid']}" Sidekiq.redis do |conn| conn.sadd('processes', key) conn.hmset(key, 'info', Sidekiq.dump_json(data), 'busy', 0, 'beat', Time.now.to_f) end ps = Sidekiq::ProcessSet.new assert_equal 1, ps.size assert_equal 1, ps.to_a.size Sidekiq.redis do |conn| conn.sadd('processes', "bar:987") conn.sadd('processes', "bar:986") end ps = Sidekiq::ProcessSet.new assert_equal 1, ps.size assert_equal 1, ps.to_a.size end def add_retry(jid = 'bob', at = Time.now.to_f) payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => jid, 'retry_count' => 2, 'failed_at' => Time.now.to_f) Sidekiq.redis do |conn| conn.zadd('retry', at.to_s, payload) end end end end end sidekiq-4.0.1/test/test_testing_inline.rb0000644000175600017570000000432312631157272017574 0ustar pravipravirequire_relative 'helper' require 'active_record' require 'action_mailer' require 'sidekiq/rails' require 'sidekiq/extensions/action_mailer' require 'sidekiq/extensions/active_record' Sidekiq.hook_rails! class TestInline < Sidekiq::Test describe 'sidekiq inline testing' do class InlineError < RuntimeError; end class ParameterIsNotString < RuntimeError; end class InlineWorker include Sidekiq::Worker def perform(pass) raise ArgumentError, "no jid" unless jid raise InlineError unless pass end end class InlineWorkerWithTimeParam include Sidekiq::Worker def perform(time) raise ParameterIsNotString unless time.is_a?(String) || time.is_a?(Numeric) end end class InlineFooMailer < ActionMailer::Base def bar(str) raise InlineError end end class InlineFooModel < ActiveRecord::Base def self.bar(str) raise InlineError end end before do require 'sidekiq/testing/inline' Sidekiq::Testing.inline! end after do Sidekiq::Testing.disable! end it 'stubs the async call when in testing mode' do assert InlineWorker.perform_async(true) assert_raises InlineError do InlineWorker.perform_async(false) end end it 'stubs the delay call on mailers' do assert_raises InlineError do InlineFooMailer.delay.bar('three') end end it 'stubs the delay call on models' do assert_raises InlineError do InlineFooModel.delay.bar('three') end end it 'stubs the enqueue call when in testing mode' do assert Sidekiq::Client.enqueue(InlineWorker, true) assert_raises InlineError do Sidekiq::Client.enqueue(InlineWorker, false) end end it 'stubs the push_bulk call when in testing mode' do assert Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [true]]}) assert_raises InlineError do Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [false]]}) end end it 'should relay parameters through json' do assert Sidekiq::Client.enqueue(InlineWorkerWithTimeParam, Time.now) end end end sidekiq-4.0.1/Pro-Changes.md0000644000175600017570000002745512631157272014661 0ustar pravipraviSidekiq Pro Changelog ======================= Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy. 3.0.0.pre2 ----------- - Fix excessive connection usage by reliable fetch. 3.0.0.pre1 ----------- - See the [Pro 3.0 release notes](Pro-3.0-Upgrade.md). 2.1.3 ----------- - Don't enable strict priority if using weighted queueing like `-q a,1 -q b,1` - Safer JSON mangling in Lua [#2639] 2.1.2 ----------- - Lock Sidekiq Pro 2.x to Sidekiq 3.x. 2.1.1 ----------- - Make ShardSet lazier so Redis can first be initialized at startup. [#2603] 2.1.0 ----------- - Explicit support for sharding batches. You list your Redis shards and Sidekiq Pro will randomly spread batches across the shards. The BID will indicate which shard contains the batch data. Jobs within a batch may be spread across all shards too. [#2548, jonhyman] - Officially deprecate Sidekiq::Notifications code. Notifications have been undocumented for months now. [#2575] 2.0.8 ----------- - Fix reliable scheduler mangling large numeric arguments. Lua's CJSON library cannot accurately encode numbers larger than 14 digits! [#2478] 2.0.7 ----------- - Optimize delete of enormous batches (100,000s of jobs) [#2458] 2.0.6, 1.9.3 -------------- - CSRF protection in Sidekiq 3.4.2 broke job filtering in the Web UI [#2442] - Sidekiq Pro 1.x is now limited to Sidekiq < 3.5.0. 2.0.5 ----------- - Atomic scheduler now sets `enqueued_at` [#2414] - Batches now account for jobs which are stopped by client middleware [#2406] - Ignore redundant calls to `Sidekiq::Client.reliable_push!` [#2408] 2.0.4 ----------- - Reliable push now supports sharding [#2409] - Reliable push now only catches Redis exceptions [#2307] 2.0.3 ----------- - Display Batch callback data on the Batch details page. [#2347] - Fix incompatibility with Pro Web and Rack middleware. [#2344] Thank you to Jason Clark for the tip on how to fix it. 2.0.2 ----------- - Multiple Web UIs can now run in the same process. [#2267] If you have multiple Redis shards, you can mount UIs for all in the same process: ```ruby POOL1 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6379/0") } POOL2 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6378/0") } mount Sidekiq::Pro::Web => '/sidekiq' # default mount Sidekiq::Pro::Web.with(redis_pool: POOL1), at: '/sidekiq1', as: 'sidekiq1' # shard1 mount Sidekiq::Pro::Web.with(redis_pool: POOL2), at: '/sidekiq2', as: 'sidekiq2' # shard2 ``` - **SECURITY** Fix batch XSS in error data. Thanks to moneybird.com for reporting the issue. 2.0.1 ----------- - Add `batch.callback_queue` so batch callbacks can use a higher priority queue than jobs. [#2200] - Gracefully recover if someone runs `SCRIPT FLUSH` on Redis. [#2240] - Ignore errors when attempting `bulk_requeue`, allowing clean shutdown 2.0.0 ----------- - See [the Upgrade Notes](Pro-2.0-Upgrade.md) for detailed notes. 1.9.2 ----------- - As of 1/1/2015, Sidekiq Pro is hosted on a new dedicated server. Happy new year and let's hope for 100% uptime! - Fix bug in reliable\_fetch where jobs could be duplicated if a Sidekiq process crashed and you were using weighted queues. [#2120] 1.9.1 ----------- - **SECURITY** Fix XSS in batch description, thanks to intercom.io for reporting the issue. If you don't use batch descriptions, you don't need the fix. 1.9.0 ----------- - Add new expiring jobs feature [#1982] - Show batch expiration on Batch details page [#1981] - Add '$' batch success token to the pubsub support. [#1953] 1.8.0 ----------- - Fix race condition where Batches can complete before they have been fully defined or only half-defined. Requires Sidekiq 3.2.3. [#1919] 1.7.6 ----------- - Quick release to verify #1919 1.7.5 ----------- - Fix job filtering within the Dead tab. - Add APIs and wiki documentation for invalidating jobs within a batch. 1.7.4 ----------- - Awesome ANSI art startup banner! 1.7.3 ----------- - Batch callbacks should use the same queue as the associated jobs. 1.7.2 ----------- - **DEPRECATION** Use `Batch#on(:complete)` instead of `Batch#notify`. The specific Campfire, HipChat, email and other notification schemes will be removed in 2.0.0. - Remove batch from UI when successful. [#1745] - Convert batch callbacks to be asynchronous jobs for error handling [#1744] 1.7.1 ----------- - Fix for paused queues being processed for a few seconds when starting a new Sidekiq process. - Add a 5 sec delay when starting reliable fetch on Heroku to minimize any duplicate job processing with another process shutting down. 1.7.0 ----------- - Add ability to pause reliable queues via API. ```ruby q = Sidekiq::Queue.new("critical") q.pause! q.paused? # => true q.unpause! ``` Sidekiq polls Redis every 10 seconds for paused queues so pausing will take a few seconds to take effect. 1.6.0 ----------- - Compatible with Sidekiq 3. 1.5.1 ----------- - Due to a breaking API change in Sidekiq 3.0, this version is limited to Sidekiq 2.x. 1.5.0 ----------- - Fix issue on Heroku where reliable fetch could orphan jobs [#1573] 1.4.3 ----------- - Reverse sorting of Batches in Web UI [#1098] - Refactoring for Sidekiq 3.0, Pro now requires Sidekiq 2.17.5 1.4.2 ----------- - Tolerate expired Batches in the web UI. - Fix 100% CPU usage when using weighted queues and reliable fetch. 1.4.1 ----------- - Add batch progress bar to batch detail page. [#1398] - Fix race condition in initializing Lua scripts 1.4.0 ----------- - Default batch expiration has been extended to 3 days, from 1 day previously. - Batches now sort in the Web UI according to expiry time, not creation time. - Add user-configurable batch expiry. If your batches might take longer than 72 hours to process, you can extend the expiration date. ```ruby b = Sidekiq::Batch.new b.expires_in 5.days ... ``` 1.3.2 ----------- - Lazy load Lua scripts so a Redis connection is not required on bootup. 1.3.1 ----------- - Fix a gemspec packaging issue which broke the Batch UI. 1.3.0 ----------- Thanks to @jonhyman for his contributions to this Sidekiq Pro release. This release includes new functionality based on the SCAN command newly added to Redis 2.8. Pro still works with Redis 2.4 but some functionality will be unavailable. - Job Filtering in the Web UI! You can now filter retries and scheduled jobs in the Web UI so you only see the jobs relevant to your needs. Queues cannot be filtered; Redis does not provide the same SCAN operation on the LIST type. **Redis 2.8** ![Filtering](https://f.cloud.github.com/assets/2911/1619465/f47529f2-5657-11e3-8cd1-33899eb72aad.png) - SCAN support in the Sidekiq::SortedSet API. Here's an example that finds all jobs which contain the substring "Warehouse::OrderShip" and deletes all matching retries. If the set is large, this API will be **MUCH** faster than standard iteration using each. **Redis 2.8** ```ruby Sidekiq::RetrySet.new.scan("Warehouse::OrderShip") do |job| job.delete end ``` - Sidekiq::Batch#jobs now returns the set of JIDs added to the batch. - Sidekiq::Batch#jids returns the complete set of JIDs associated with the batch. - Sidekiq::Batch#remove\_jobs(jid, jid, ...) removes JIDs from the set, allowing early termination of jobs if they become irrelevant according to application logic. - Sidekiq::Batch#include?(jid) allows jobs to check if they are still relevant to a Batch and exit early if not. - Sidekiq::SortedSet#find\_job(jid) now uses server-side Lua if possible **Redis 2.6** [jonhyman] - The statsd integration now sets global job counts: ```ruby jobs.count jobs.success jobs.failure ``` - Change shutdown logic to push leftover jobs in the private queue back into the public queue when shutting down with Reliable Fetch. This allows the safe decommission of a Sidekiq Pro process when autoscaling. [jonhyman] - Add support for weighted random fetching with Reliable Fetch [jonhyman] - Pro now requires Sidekiq 2.17.0 1.2.5 ----------- - Convert Batch UI to use Sidekiq 2.16's support for extension localization. - Update reliable\_push to work with Sidekiq::Client refactoring in 2.16 - Pro now requires Sidekiq 2.16.0 1.2.4 ----------- - Convert Batch UI to Bootstrap 3 - Pro now requires Sidekiq 2.15.0 - Add Sidekiq::Batch::Status#delete [#1205] 1.2.3 ----------- - Pro now requires Sidekiq 2.14.0 - Fix bad exception handling in batch callbacks [#1134] - Convert Batch UI to ERB 1.2.2 ----------- - Problem with reliable fetch which could lead to lost jobs when Sidekiq is shut down normally. Thanks to MikaelAmborn for the report. [#1109] 1.2.1 ----------- - Forgot to push paging code necessary for `delete_job` performance. 1.2.0 ----------- - **LEAK** Fix batch key which didn't expire in Redis. Keys match /b-[a-f0-9]{16}-pending/, e.g. "b-4f55163ddba10aa0-pending" [#1057] - **Reliable fetch now supports multiple queues**, using the algorithm spec'd by @jackrg [#1102] - Fix issue with reliable\_push where it didn't return the JID for a pushed job when sending previously cached jobs to Redis. - Add fast Sidekiq::Queue#delete\_job(jid) API which leverages Lua so job lookup is 100% server-side. Benchmark vs Sidekiq's Job#delete API. **Redis 2.6** ``` Sidekiq Pro API 0.030000 0.020000 0.050000 ( 1.640659) Sidekiq API 17.250000 2.220000 19.470000 ( 22.193300) ``` - Add fast Sidekiq::Queue#delete\_by\_class(klass) API to remove all jobs of a given type. Uses server-side Lua for performance. **Redis 2.6** 1.1.0 ----------- - New `sidekiq/pro/reliable_push` which makes Sidekiq::Client resiliant to Redis network failures. [#793] - Move `sidekiq/reliable_fetch` to `sidekiq/pro/reliable_fetch` 1.0.0 ----------- - Sidekiq Pro changelog moved to mperham/sidekiq for public visibility. - Add new Rack endpoint for easy polling of batch status via JavaScript. See `sidekiq/rack/batch_status` 0.9.3 ----------- - Fix bad /batches path in Web UI - Fix Sinatra conflict with sidekiq-failures 0.9.2 ----------- - Fix issue with lifecycle notifications not firing. 0.9.1 ----------- - Update due to Sidekiq API changes. 0.9.0 ----------- - Rearchitect Sidekiq's Fetch code to support different fetch strategies. Add a ReliableFetch strategy which works with Redis' RPOPLPUSH to ensure we don't lose messages, even when the Sidekiq process crashes unexpectedly. [mperham/sidekiq#607] 0.8.2 ----------- - Reimplement existing notifications using batch on_complete events. 0.8.1 ----------- - Rejigger batch callback notifications. 0.8.0 ----------- - Add new Batch 'callback' notification support, for in-process notification. - Symbolize option keys passed to Pony [mperham/sidekiq#603] - Batch no longer requires the Web UI since Web UI usage is optional. You must require is manually in your Web process: ```ruby require 'sidekiq/web' require 'sidekiq/batch/web' mount Sidekiq::Web => '/sidekiq' ``` 0.7.1 ----------- - Worker instances can access the associated jid and bid via simple accessors. - Batches can now be modified while being processed so, e.g. a batch job can add additional jobs to its own batch. ```ruby def perform(...) batch = Sidekiq::Batch.new(bid) # instantiate batch associated with this job batch.jobs do SomeWorker.perform_async # add another job end end ``` - Save error backtraces in batch's failure info for display in Web UI. - Clean up email notification a bit. 0.7.0 ----------- - Add optional batch description - Mutable batches. Batches can now be modified to add additional jobs at runtime. Example would be a batch job which needs to create more jobs based on the data it is processing. ```ruby batch = Sidekiq::Batch.new(bid) batch.jobs do # define more jobs here end ``` - Fix issues with symbols vs strings in option hashes 0.6.1 ----------- - Webhook notification support 0.6 ----------- - Redis pubsub - Email polish 0.5 ----------- - Batches - Notifications - Statsd middleware sidekiq-4.0.1/.gitignore0000644000175600017570000000015012631157272014200 0ustar pravipravi.rvmrc .ruby-version tags Gemfile.lock *.swp dump.rdb .rbx coverage/ vendor/ .bundle/ .sass-cache/ tmp/ sidekiq-4.0.1/4.0-Upgrade.md0000644000175600017570000000274112631157272014430 0ustar pravipravi# Welcome to Sidekiq 4.0! Sidekiq 4.0 contains a redesigned, more efficient core with less overhead per job. See my blog for [an overview of Sidekiq 4's higher performance](http://www.mikeperham.com/2015/10/14/optimizing-sidekiq/). ## What's New * Sidekiq no longer uses Celluloid. If your application code uses Celluloid, you will need to pull it in yourself. * `redis-namespace` has been removed from Sidekiq's gem dependencies. If you want to use namespacing ([and I strongly urge you not to](http://www.mikeperham.com/2015/09/24/storing-data-with-redis/)), you'll need to add the gem to your Gemfile: ```ruby gem 'redis-namespace' ``` * **Redis 2.8.0 or greater is required.** Redis 2.8 was released two years ago and contains **many** useful features which Sidekiq couldn't leverage until now. **Redis 3.0.3 or greater is recommended** for large scale use [#2431](https://github.com/mperham/sidekiq/issues/2431). * Jobs are now fetched from Redis in parallel, making Sidekiq more resilient to high network latency. This means that Sidekiq requires more Redis connections per process. You must have a minimum of `concurrency + 2` connections in your pool or Sidekiq will exit. When in doubt, let Sidekiq size the connection pool for you. ## Upgrade First, make sure you are using Redis 2.8 or greater. Next: * Upgrade to the latest Sidekiq 3.x. ```ruby gem 'sidekiq', '< 4' ``` * Fix any deprecation warnings you see. * Upgrade to 4.x. ```ruby gem 'sidekiq', '< 5' ``` sidekiq-4.0.1/Rakefile0000644000175600017570000000030112631157272013653 0ustar pravipravirequire 'bundler/gem_tasks' require 'rake/testtask' Rake::TestTask.new(:test) do |test| #SO MUCH NOISE #test.warning = true test.pattern = 'test/**/test_*.rb' end task :default => :test sidekiq-4.0.1/README.md0000644000175600017570000001034112631157272013472 0ustar pravipraviSidekiq ============== [![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq) [![Code Climate](https://codeclimate.com/github/mperham/sidekiq.svg)](https://codeclimate.com/github/mperham/sidekiq) [![Build Status](https://travis-ci.org/mperham/sidekiq.svg)](https://travis-ci.org/mperham/sidekiq) [![Gitter Chat](https://badges.gitter.im/mperham/sidekiq.svg)](https://gitter.im/mperham/sidekiq) Simple, efficient background processing for Ruby. Sidekiq uses threads to handle many jobs at the same time in the same process. It does not require Rails but will integrate tightly with Rails 3/4 to make background processing dead simple. Sidekiq is compatible with Resque. It uses the exact same message format as Resque so it can integrate into an existing Resque processing farm. You can have Sidekiq and Resque run side-by-side at the same time and use the Resque client to enqueue jobs in Redis to be processed by Sidekiq. At the same time, Sidekiq uses multithreading so it is much more memory efficient than Resque (which forks a new process for every job). You'll find that you might need 10 200MB resque processes to peg your CPU whereas one 300MB Sidekiq process will peg the same CPU and perform the same amount of work. Requirements ----------------- I test with the latest MRI (2.2, 2.1 and 2.0) and JRuby versions (1.7). Other versions/VMs are untested but might work fine. MRI 1.9 is no longer supported. All Rails releases starting from 3.2 are officially supported. Redis 2.8 or greater is required. Installation ----------------- gem install sidekiq Getting Started ----------------- See the [Getting Started wiki page](https://github.com/mperham/sidekiq/wiki/Getting-Started) and follow the simple setup process. You can watch [Railscast #366](http://railscasts.com/episodes/366-sidekiq) to see Sidekiq in action. If you do everything right, you should see this: ![Web UI](https://github.com/mperham/sidekiq/raw/master/examples/web-ui.png) Want to Upgrade? ------------------- I also sell Sidekiq Pro and Sidekiq Enterprise, extensions to Sidekiq which provide more features, a commercial-friendly license and allow you to support high quality open source development all at the same time. Please see the [Sidekiq](http://sidekiq.org/) homepage for more detail. More Information ----------------- Please see the [sidekiq wiki](https://github.com/mperham/sidekiq/wiki) for the official documentation. [mperham/sidekiq on Gitter](https://gitter.im/mperham/sidekiq) is dedicated to this project, but bug reports or feature requests suggestions should still go through [issues on Github](https://github.com/mperham/sidekiq/issues). Release announcements are made to the [@sidekiq](https://twitter.com/sidekiq) Twitter account. You may also find useful a [Reddit area](https://reddit.com/r/sidekiq) dedicated to Sidekiq discussion and [a Sidekiq tag](https://stackoverflow.com/questions/tagged/sidekiq) on Stack Overflow. Problems? ----------------- **Please do not directly email any Sidekiq committers with questions or problems.** A community is best served when discussions are held in public. If you have a problem, please review the [FAQ](https://github.com/mperham/sidekiq/wiki/FAQ) and [Troubleshooting](https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting) wiki pages. Searching the issues for your problem is also a good idea. If that doesn't help, feel free to email the Sidekiq mailing list, chat in Gitter, or open a new issue. StackOverflow or Reddit is the preferred place to ask questions on usage. If you are encountering what you think is a bug, please open an issue. Thanks ----------------- Sidekiq stays fast by using the [JProfiler java profiler](http://www.ej-technologies.com/products/jprofiler/overview.html) to find and fix performance problems on JRuby. Unfortunately MRI does not have good multithreaded profiling tools. License ----------------- Please see [LICENSE](https://github.com/mperham/sidekiq/blob/master/LICENSE) for licensing details. Author ----------------- Mike Perham, [@mperham](https://twitter.com/mperham) / [@sidekiq](https://twitter.com/sidekiq), [http://www.mikeperham.com](http://www.mikeperham.com) / [http://www.contribsys.com](http://www.contribsys.com) sidekiq-4.0.1/.travis.yml0000644000175600017570000000031012631157272014317 0ustar pravipravilanguage: ruby sudo: false cache: bundler services: - redis-server rvm: - 2.1 - 2.0.0 - 2.2 - jruby - jruby-head - rbx-2 matrix: allow_failures: - rvm: rbx-2 - rvm: jruby-head sidekiq-4.0.1/metadata.yml0000644000175600017570000002665012631157272014530 0ustar pravipravi--- !ruby/object:Gem::Specification name: sidekiq version: !ruby/object:Gem::Version version: 4.0.1 platform: ruby authors: - Mike Perham autorequire: bindir: bin cert_chain: [] date: 2015-11-17 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: redis requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.2' - - ">=" - !ruby/object:Gem::Version version: 3.2.1 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.2' - - ">=" - !ruby/object:Gem::Version version: 3.2.1 - !ruby/object:Gem::Dependency name: connection_pool requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.2' - - ">=" - !ruby/object:Gem::Version version: 2.2.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.2' - - ">=" - !ruby/object:Gem::Version version: 2.2.0 - !ruby/object:Gem::Dependency name: json requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' - !ruby/object:Gem::Dependency name: concurrent-ruby requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' - !ruby/object:Gem::Dependency name: redis-namespace requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.5' - - ">=" - !ruby/object:Gem::Version version: 1.5.2 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.5' - - ">=" - !ruby/object:Gem::Version version: 1.5.2 - !ruby/object:Gem::Dependency name: sinatra requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.4' - - ">=" - !ruby/object:Gem::Version version: 1.4.6 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.4' - - ">=" - !ruby/object:Gem::Version version: 1.4.6 - !ruby/object:Gem::Dependency name: minitest requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '5.7' - - ">=" - !ruby/object:Gem::Version version: 5.7.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '5.7' - - ">=" - !ruby/object:Gem::Version version: 5.7.0 - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.0' - !ruby/object:Gem::Dependency name: rails requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '4' - - ">=" - !ruby/object:Gem::Version version: 3.2.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '4' - - ">=" - !ruby/object:Gem::Version version: 3.2.0 description: Simple, efficient background processing for Ruby. email: - mperham@gmail.com executables: - sidekiq - sidekiqctl extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - 3.0-Upgrade.md - 4.0-Upgrade.md - COMM-LICENSE - Changes.md - Contributing.md - Ent-Changes.md - Gemfile - LICENSE - Pro-2.0-Upgrade.md - Pro-3.0-Upgrade.md - Pro-Changes.md - README.md - Rakefile - bin/sidekiq - bin/sidekiqctl - bin/sidekiqload - lib/generators/sidekiq/templates/worker.rb.erb - lib/generators/sidekiq/templates/worker_spec.rb.erb - lib/generators/sidekiq/templates/worker_test.rb.erb - lib/generators/sidekiq/worker_generator.rb - lib/sidekiq.rb - lib/sidekiq/api.rb - lib/sidekiq/cli.rb - lib/sidekiq/client.rb - lib/sidekiq/core_ext.rb - lib/sidekiq/exception_handler.rb - lib/sidekiq/extensions/action_mailer.rb - lib/sidekiq/extensions/active_record.rb - lib/sidekiq/extensions/class_methods.rb - lib/sidekiq/extensions/generic_proxy.rb - lib/sidekiq/fetch.rb - lib/sidekiq/launcher.rb - lib/sidekiq/logging.rb - lib/sidekiq/manager.rb - lib/sidekiq/middleware/chain.rb - lib/sidekiq/middleware/i18n.rb - lib/sidekiq/middleware/server/active_record.rb - lib/sidekiq/middleware/server/logging.rb - lib/sidekiq/middleware/server/retry_jobs.rb - lib/sidekiq/paginator.rb - lib/sidekiq/processor.rb - lib/sidekiq/rails.rb - lib/sidekiq/redis_connection.rb - lib/sidekiq/scheduled.rb - lib/sidekiq/testing.rb - lib/sidekiq/testing/inline.rb - lib/sidekiq/util.rb - lib/sidekiq/version.rb - lib/sidekiq/web.rb - lib/sidekiq/web_helpers.rb - lib/sidekiq/worker.rb - sidekiq.gemspec - test/config.yml - test/env_based_config.yml - test/fake_env.rb - test/fixtures/en.yml - test/helper.rb - test/test_actors.rb - test/test_api.rb - test/test_cli.rb - test/test_client.rb - test/test_exception_handler.rb - test/test_extensions.rb - test/test_fetch.rb - test/test_launcher.rb - test/test_logging.rb - test/test_manager.rb - test/test_middleware.rb - test/test_processor.rb - test/test_rails.rb - test/test_redis_connection.rb - test/test_retry.rb - test/test_scheduled.rb - test/test_scheduling.rb - test/test_sidekiq.rb - test/test_testing.rb - test/test_testing_fake.rb - test/test_testing_inline.rb - test/test_util.rb - test/test_web.rb - test/test_web_helpers.rb - web/assets/images/bootstrap/glyphicons-halflings-white.png - web/assets/images/bootstrap/glyphicons-halflings.png - web/assets/images/favicon.ico - web/assets/images/logo.png - web/assets/images/status-sd8051fd480.png - web/assets/images/status/active.png - web/assets/images/status/idle.png - web/assets/javascripts/application.js - web/assets/javascripts/dashboard.js - web/assets/javascripts/locales/README.md - web/assets/javascripts/locales/jquery.timeago.ar.js - web/assets/javascripts/locales/jquery.timeago.bg.js - web/assets/javascripts/locales/jquery.timeago.bs.js - web/assets/javascripts/locales/jquery.timeago.ca.js - web/assets/javascripts/locales/jquery.timeago.cs.js - web/assets/javascripts/locales/jquery.timeago.cy.js - web/assets/javascripts/locales/jquery.timeago.da.js - web/assets/javascripts/locales/jquery.timeago.de.js - web/assets/javascripts/locales/jquery.timeago.el.js - web/assets/javascripts/locales/jquery.timeago.en-short.js - web/assets/javascripts/locales/jquery.timeago.en.js - web/assets/javascripts/locales/jquery.timeago.es.js - web/assets/javascripts/locales/jquery.timeago.et.js - web/assets/javascripts/locales/jquery.timeago.fa.js - web/assets/javascripts/locales/jquery.timeago.fi.js - web/assets/javascripts/locales/jquery.timeago.fr-short.js - web/assets/javascripts/locales/jquery.timeago.fr.js - web/assets/javascripts/locales/jquery.timeago.he.js - web/assets/javascripts/locales/jquery.timeago.hr.js - web/assets/javascripts/locales/jquery.timeago.hu.js - web/assets/javascripts/locales/jquery.timeago.hy.js - web/assets/javascripts/locales/jquery.timeago.id.js - web/assets/javascripts/locales/jquery.timeago.it.js - web/assets/javascripts/locales/jquery.timeago.ja.js - web/assets/javascripts/locales/jquery.timeago.ko.js - web/assets/javascripts/locales/jquery.timeago.lt.js - web/assets/javascripts/locales/jquery.timeago.mk.js - web/assets/javascripts/locales/jquery.timeago.nl.js - web/assets/javascripts/locales/jquery.timeago.no.js - web/assets/javascripts/locales/jquery.timeago.pl.js - web/assets/javascripts/locales/jquery.timeago.pt-br.js - web/assets/javascripts/locales/jquery.timeago.pt.js - web/assets/javascripts/locales/jquery.timeago.ro.js - web/assets/javascripts/locales/jquery.timeago.rs.js - web/assets/javascripts/locales/jquery.timeago.ru.js - web/assets/javascripts/locales/jquery.timeago.sk.js - web/assets/javascripts/locales/jquery.timeago.sl.js - web/assets/javascripts/locales/jquery.timeago.sv.js - web/assets/javascripts/locales/jquery.timeago.th.js - web/assets/javascripts/locales/jquery.timeago.tr.js - web/assets/javascripts/locales/jquery.timeago.uk.js - web/assets/javascripts/locales/jquery.timeago.uz.js - web/assets/javascripts/locales/jquery.timeago.zh-cn.js - web/assets/javascripts/locales/jquery.timeago.zh-tw.js - web/assets/stylesheets/application.css - web/assets/stylesheets/bootstrap.css - web/locales/cs.yml - web/locales/da.yml - web/locales/de.yml - web/locales/el.yml - web/locales/en.yml - web/locales/es.yml - web/locales/fr.yml - web/locales/hi.yml - web/locales/it.yml - web/locales/ja.yml - web/locales/ko.yml - web/locales/nb.yml - web/locales/nl.yml - web/locales/pl.yml - web/locales/pt-br.yml - web/locales/pt.yml - web/locales/ru.yml - web/locales/sv.yml - web/locales/ta.yml - web/locales/uk.yml - web/locales/zh-cn.yml - web/locales/zh-tw.yml - web/views/_footer.erb - web/views/_job_info.erb - web/views/_nav.erb - web/views/_paging.erb - web/views/_poll_js.erb - web/views/_poll_link.erb - web/views/_status.erb - web/views/_summary.erb - web/views/busy.erb - web/views/dashboard.erb - web/views/dead.erb - web/views/layout.erb - web/views/morgue.erb - web/views/queue.erb - web/views/queues.erb - web/views/retries.erb - web/views/retry.erb - web/views/scheduled.erb - web/views/scheduled_job_info.erb homepage: http://sidekiq.org licenses: - LGPL-3.0 metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.5 signing_key: specification_version: 4 summary: Simple, efficient background processing for Ruby test_files: - test/config.yml - test/env_based_config.yml - test/fake_env.rb - test/fixtures/en.yml - test/helper.rb - test/test_actors.rb - test/test_api.rb - test/test_cli.rb - test/test_client.rb - test/test_exception_handler.rb - test/test_extensions.rb - test/test_fetch.rb - test/test_launcher.rb - test/test_logging.rb - test/test_manager.rb - test/test_middleware.rb - test/test_processor.rb - test/test_rails.rb - test/test_redis_connection.rb - test/test_retry.rb - test/test_scheduled.rb - test/test_scheduling.rb - test/test_sidekiq.rb - test/test_testing.rb - test/test_testing_fake.rb - test/test_testing_inline.rb - test/test_util.rb - test/test_web.rb - test/test_web_helpers.rb sidekiq-4.0.1/Pro-2.0-Upgrade.md0000644000175600017570000001057512631157272015170 0ustar pravipravi# Upgrading to Sidekiq Pro 2.0 Sidekiq Pro 2.0 allows nested batches for more complex job workflows and provides a new reliable scheduler which uses Lua to guarantee atomicity and much higher performance. It also removes deprecated APIs, changes the batch data format and how features are activated. Read carefully to ensure your upgrade goes smoothly. Sidekiq Pro 2.0 requires Sidekiq 3.3.2 or greater. Redis 2.8 is recommended; Redis 2.4 or 2.6 will work but some functionality will not be available. **Note that you CANNOT go back to Pro 1.x once you've created batches with 2.x. The new batches will not process correctly with 1.x.** **If you are on a version of Sidekiq Pro <1.5, you should upgrade to the latest 1.x version and run it for a week before upgrading to 2.0.** ## Nested Batches Batches can now be nested within the `jobs` method. This feature enables Sidekiq Pro to handle workflow processing of any size and complexity! ```ruby a = Sidekiq::Batch.new a.on(:success, SomeCallback) a.jobs do SomeWork.perform_async b = Sidekiq::Batch.new b.on(:success, MyCallback) b.jobs do OtherWork.perform_async end end ``` Parent batch callbacks are not processed until all child batch callbacks have run successfully. In the example above, `MyCallback` will always fire before `SomeCallback` because `b` is considered a child of `a`. Of course you can dynamically add child batches while a batch job is executing. ```ruby def perform(*args) do_something(args) if more_work? # Sidekiq::Worker#batch returns the Batch this job is part of. batch.jobs do b = Sidekiq::Batch.new b.on(:success, MyCallback) b.jobs do OtherWork.perform_async end end end end ``` More context: [#1485] ## Batch Data The batch data model was overhauled. Batch data should take significantly less space in Redis now. A simple benchmark shows 25% savings but real world savings should be even greater. * Batch 2.x BIDs are 14 character URL-safe Base64-encoded strings, e.g. "vTF1-9QvLPnREQ". Batch 1.x BIDs were 16 character hex-encoded strings, e.g. "4a3fc67d30370edf". * In 1.x, batch data was not removed until it naturally expired in Redis. In 2.x, all data for a batch is removed from Redis once the batch has run any success callbacks. * Because of the former point, batch expiry is no longer a concern. Batch expiry is hardcoded to 30 days and is no longer user-tunable. * Failed batch jobs no longer automatically store any associated backtrace in Redis. **There's no data migration required. Sidekiq Pro 2.0 transparently handles both old and new format.** More context: [#2130] ## Reliability 2.0 brings a new reliable scheduler which uses Lua inside Redis so enqueuing scheduled jobs is atomic. Benchmarks show it 50x faster when enqueuing lots of jobs. **Two caveats**: - Client-side middleware is not executed for each job when enqueued with the reliable scheduler. No Sidekiq or Sidekiq Pro functionality is affected by this change but some 3rd party plugins might be. - The Lua script used inside the reliable scheduler is not safe for use with Redis Cluster or other multi-master Redis solutions. It is safe to use with Redis Sentinel or a typical master/slave replication setup. **You no longer require anything to use the Reliability features.** * Activate reliable fetch and/or the new reliable scheduler: ```ruby Sidekiq.configure_server do |config| config.reliable_fetch! config.reliable_scheduler! end ``` * Activate reliable push: ```ruby Sidekiq::Client.reliable_push! ``` More context: [#2130] ## Other Changes * You must require `sidekiq/pro/notifications` if you want to use the existing notification schemes. I don't recommend using them as the newer-style `Sidekiq::Batch#on` method is simpler and more flexible. * Several classes have been renamed. Generally these classes are ones you should not need to require/use in your own code, e.g. the Batch middleware. * You can add `attr_accessor :jid` to a Batch callback class and Sidekiq Pro will set it to the jid of the callback job. [#2178] * There's now an official API to iterate all known Batches [#2191] ```ruby Sidekiq::BatchSet.new.each {|status| p status.bid } ``` * The Web UI now shows the Sidekiq Pro version in the footer. [#1991] ## Thanks Adam Prescott, Luke van der Hoeven and Jon Hyman all provided valuable feedback during the release process. Thank you guys! sidekiq-4.0.1/Changes.md0000644000175600017570000011266312631157272014117 0ustar pravipravi# Sidekiq Changes 4.0.1 ----------- - Yank new queue-based testing API [#2663] - Fix invalid constant reference in heartbeat 4.0.0 ----------- - Sidekiq's internals have been completely overhauled for performance and to remove dependencies. This has resulted in major speedups, as [detailed on my blog](http://www.mikeperham.com/2015/10/14/optimizing-sidekiq/). - See the [4.0 upgrade notes](4.0-Upgrade.md) for more detail. 3.5.3 ----------- - Adjust shutdown event to run in parallel with the rest of system shutdown. [#2635] 3.5.2 ----------- - **Sidekiq 3 is now in maintenance mode**, only major bugs will be fixed. - The exception triggering a retry is now passed into `sidekiq_retry_in`, allowing you to retry more frequently for certain types of errors. [#2619, kreynolds] ```ruby sidekiq_retry_in do |count, ex| case ex when RuntimeError 5 * count else 10 * count end end ``` 3.5.1 ----------- - **FIX MEMORY LEAK** Under rare conditions, threads may leak [#2598, gazay] - Add Ukranian locale [#2561, elrakita] - Disconnect and retry Redis operations if we see a READONLY error [#2550] - Add server middleware testing harness; see [wiki](https://github.com/mperham/sidekiq/wiki/Testing#testing-server-middleware) [#2534, ryansch] 3.5.0 ----------- - Polished new banner! [#2522, firedev] - Upgrade to Celluloid 0.17. [#2420, digitalextremist] - Activate sessions in Sinatra for CSRF protection, requires Rails monkeypatch due to rails/rails#15843. [#2460, jc00ke] 3.4.2 ----------- - Don't allow `Sidekiq::Worker` in ActiveJob::Base classes. [#2424] - Safer display of job data in Web UI [#2405] - Fix CSRF vulnerability in Web UI, thanks to Egor Homakov for reporting. [#2422] If you are running the Web UI as a standalone Rack app, ensure you have a [session middleware configured](https://github.com/mperham/sidekiq/wiki/Monitoring#standalone): ```ruby use Rack::Session::Cookie, :secret => "some unique secret string here" ``` 3.4.1 ----------- - Lock to Celluloid 0.16 3.4.0 ----------- - Set a `created_at` attribute when jobs are created, set `enqueued_at` only when they go into a queue. Fixes invalid latency calculations with scheduled jobs. [#2373, mrsimo] - Don't log timestamp on Heroku [#2343] - Run `shutdown` event handlers in reverse order of definition [#2374] - Rename and rework `poll_interval` to be simpler, more predictable [#2317, cainlevy] The new setting is `average_scheduled_poll_interval`. To configure Sidekiq to look for scheduled jobs every 5 seconds, just set it to 5. ```ruby Sidekiq.configure_server do |config| config.average_scheduled_poll_interval = 5 end ``` 3.3.4 ----------- - **Improved ActiveJob integration** - Web UI now shows ActiveJobs in a nicer format and job logging shows the actual class name, requires Rails 4.2.2+ [#2248, #2259] - Add Sidekiq::Process#dump\_threads API to trigger TTIN output [#2247] - Web UI polling now uses Ajax to avoid page reload [#2266, davydovanton] - Several Web UI styling improvements [davydovanton] - Add Tamil, Hindi translations for Web UI [ferdinandrosario, tejasbubane] - Fix Web UI to work with country-specific locales [#2243] - Handle circular error causes [#2285, eugenk] 3.3.3 ----------- - Fix crash on exit when Redis is down [#2235] - Fix duplicate logging on startup - Undeprecate delay extension for ActionMailer 4.2+ . [#2186] 3.3.2 ----------- - Add Sidekiq::Stats#queues back - Allows configuration of dead job set size and timeout [#2173, jonhyman] - Refactor scheduler enqueuing so Sidekiq Pro can override it. [#2159] 3.3.1 ----------- - Dumb down ActionMailer integration so it tries to deliver if possible [#2149] - Stringify Sidekiq.default\_worker\_options's keys [#2126] - Add random integer to process identity [#2113, michaeldiscala] - Log Sidekiq Pro's Batch ID if available [#2076] - Refactor Processor Redis usage to avoid redis/redis-rb#490 [#2094] - Move /dashboard/stats to /stats. Add /stats/queues. [moserke, #2099] - Add processes count to /stats [ismaelga, #2141] - Greatly improve speed of Sidekiq::Stats [ismaelga, #2142] - Add better usage text for `sidekiqctl`. - `Sidekiq::Logging.with_context` is now a stack so you can set your own job context for logging purposes [grosser, #2110] - Remove usage of Google Fonts in Web UI so it loads in China [#2144] 3.3.0 ----------- - Upgrade to Celluloid 0.16 [#2056] - Fix typo for generator test file name [dlackty, #2016] - Add Sidekiq::Middleware::Chain#prepend [seuros, #2029] 3.2.6 ----------- - Deprecate delay extension for ActionMailer 4.2+ . [seuros, #1933] - Poll interval tuning now accounts for dead processes [epchris, #1984] - Add non-production environment to Web UI page titles [JacobEvelyn, #2004] 3.2.5 ----------- - Lock Celluloid to 0.15.2 due to bugs in 0.16.0. This prevents the "hang on shutdown" problem with Celluloid 0.16.0. 3.2.4 ----------- - Fix issue preventing ActionMailer sends working in some cases with Rails 4. [pbhogan, #1923] 3.2.3 ----------- - Clean invalid bytes from error message before converting to JSON (requires Ruby 2.1+) [#1705] - Add queues list for each process to the Busy page. [davetoxa, #1897] - Fix for crash caused by empty config file. [jordan0day, #1901] - Add Rails Worker generator, `rails g sidekiq:worker User` will create `app/workers/user_worker.rb`. [seuros, #1909] - Fix Web UI rendering with huge job arguments [jhass, #1918] - Minor refactoring of Sidekiq::Client internals, for Sidekiq Pro. [#1919] 3.2.2 ----------- - **This version of Sidekiq will no longer start on Ruby 1.9.** Sidekiq 3 does not support MRI 1.9 but we've allowed it to run before now. - Fix issue which could cause Sidekiq workers to disappear from the Busy tab while still being active [#1884] - Add "Back to App" button in Web UI. You can set the button link via `Sidekiq::Web.app_url = 'http://www.mysite.com'` [#1875, seuros] - Add process tag (`-g tag`) to the Busy page so you can differentiate processes at a glance. [seuros, #1878] - Add "Kill" button to move retries directly to the DJQ so they don't retry. [seuros, #1867] 3.2.1 ----------- - Revert eager loading change for Rails 3.x apps, as it broke a few edge cases. 3.2.0 ----------- - **Fix issue which caused duplicate job execution in Rails 3.x** This issue is caused by [improper exception handling in ActiveRecord](https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L281) which changes Sidekiq's Shutdown exception into a database error, making Sidekiq think the job needs to be retried. **The fix requires Ruby 2.1**. [#1805] - Update how Sidekiq eager loads Rails application code [#1791, jonleighton] - Change logging timestamp to show milliseconds. - Reverse sorting of Dead tab so newer jobs are listed first [#1802] 3.1.4 ----------- - Happy π release! - Self-tuning Scheduler polling, we use heartbeat info to better tune poll\_interval [#1630] - Remove all table column width rules, hopefully get better column formatting [#1747] - Handle edge case where YAML can't be decoded in dev mode [#1761] - Fix lingering jobs in Busy page on Heroku [#1764] 3.1.3 ----------- - Use ENV['DYNO'] on Heroku for hostname display, rather than an ugly UUID. [#1742] - Show per-process labels on the Busy page, for feature tagging [#1673] 3.1.2 ----------- - Suitably chastised, @mperham reverts the Bundler change. 3.1.1 ----------- - Sidekiq::CLI now runs `Bundler.require(:default, environment)` to boot all gems before loading any app code. - Sort queues by name in Web UI [#1734] 3.1.0 ----------- - New **remote control** feature: you can remotely trigger Sidekiq to quiet or terminate via API, without signals. This is most useful on JRuby or Heroku which does not support the USR1 'quiet' signal. Now you can run a rake task like this at the start of your deploy to quiet your set of Sidekiq processes. [#1703] ```ruby namespace :sidekiq do task :quiet => :environment do Sidekiq::ProcessSet.new.each(&:quiet!) end end ``` - The Web UI can use the API to quiet or stop all processes via the Busy page. - The Web UI understands and hides the `Sidekiq::Extensions::Delay*` classes, instead showing `Class.method` as the Job. [#1718] - Polish the Dashboard graphs a bit, update Rickshaw [brandonhilkert, #1725] - The poll interval is now configurable in the Web UI [madebydna, #1713] - Delay extensions can be removed so they don't conflict with DelayedJob: put `Sidekiq.remove_delay!` in your initializer. [devaroop, #1674] 3.0.2 ----------- - Revert gemfile requirement of Ruby 2.0. JRuby 1.7 calls itself Ruby 1.9.3 and broke with this requirement. 3.0.1 ----------- - Revert pidfile behavior from 2.17.5: Sidekiq will no longer remove its own pidfile as this is a race condition when restarting. [#1470, #1677] - Show warning on the Queues page if a queue is paused [#1672] - Only activate the ActiveRecord middleware if ActiveRecord::Base is defined on boot. [#1666] - Add ability to disable jobs going to the DJQ with the `dead` option. ```ruby sidekiq_options :dead => false, :retry => 5 ``` - Minor fixes 3.0.0 ----------- Please see [3.0-Upgrade.md](3.0-Upgrade.md) for more comprehensive upgrade notes. - **Dead Job Queue** - jobs which run out of retries are now moved to a dead job queue. These jobs must be retried manually or they will expire after 6 months or 10,000 jobs. The Web UI contains a "Dead" tab exposing these jobs. Use `sidekiq_options :retry => false` if you don't wish jobs to be retried or put in the DJQ. Use `sidekiq_options :retry => 0` if you don't want jobs to retry but go straight to the DJQ. - **Process Lifecycle Events** - you can now register blocks to run at certain points during the Sidekiq process lifecycle: startup, quiet and shutdown. ```ruby Sidekiq.configure_server do |config| config.on(:startup) do # do something end end ``` - **Global Error Handlers** - blocks of code which handle errors that occur anywhere within Sidekiq, not just within middleware. ```ruby Sidekiq.configure_server do |config| config.error_handlers << proc {|ex,ctx| ... } end ``` - **Process Heartbeat** - each Sidekiq process will ping Redis every 5 seconds to give a summary of the Sidekiq population at work. - The Workers tab is now renamed to Busy and contains a list of live Sidekiq processes and jobs in progress based on the heartbeat. - **Shardable Client** - Sidekiq::Client instances can use a custom Redis connection pool, allowing very large Sidekiq installations to scale by sharding: sending different jobs to different Redis instances. ```ruby client = Sidekiq::Client.new(ConnectionPool.new { Redis.new }) client.push(...) ``` ```ruby Sidekiq::Client.via(ConnectionPool.new { Redis.new }) do FooWorker.perform_async BarWorker.perform_async end ``` **Sharding support does require a breaking change to client-side middleware, see 3.0-Upgrade.md.** - New Chinese, Greek, Swedish and Czech translations for the Web UI. - Updated most languages translations for the new UI features. - **Remove official Capistrano integration** - this integration has been moved into the [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq) gem. - **Remove official support for MRI 1.9** - Things still might work but I no longer actively test on it. - **Remove built-in support for Redis-to-Go**. Heroku users: `heroku config:set REDIS_PROVIDER=REDISTOGO_URL` - **Remove built-in error integration for Airbrake, Honeybadger, ExceptionNotifier and Exceptional**. Each error gem should provide its own Sidekiq integration. Update your error gem to the latest version to pick up Sidekiq support. - Upgrade to connection\_pool 2.0 which now creates connections lazily. - Remove deprecated Sidekiq::Client.registered\_\* APIs - Remove deprecated support for the old Sidekiq::Worker#retries\_exhausted method. - Removed 'sidekiq/yaml\_patch', this was never documented or recommended. - Removed --profile option, #1592 - Remove usage of the term 'Worker' in the UI for clarity. Users would call both threads and processes 'workers'. Instead, use "Thread", "Process" or "Job". 2.17.7 ----------- - Auto-prune jobs older than one hour from the Workers page [#1508] - Add Sidekiq::Workers#prune which can perform the auto-pruning. - Fix issue where a job could be lost when an exception occurs updating Redis stats before the job executes [#1511] 2.17.6 ----------- - Fix capistrano integration due to missing pidfile. [#1490] 2.17.5 ----------- - Automatically use the config file found at `config/sidekiq.yml`, if not passed `-C`. [#1481] - Store 'retried\_at' and 'failed\_at' timestamps as Floats, not Strings. [#1473] - A `USR2` signal will now reopen _all_ logs, using IO#reopen. Thus, instead of creating a new Logger object, Sidekiq will now just update the existing Logger's file descriptor [#1163]. - Remove pidfile when shutting down if started with `-P` [#1470] 2.17.4 ----------- - Fix JID support in inline testing, #1454 - Polish worker arguments display in UI, #1453 - Marshal arguments fully to avoid worker mutation, #1452 - Support reverse paging sorted sets, #1098 2.17.3 ----------- - Synchronously terminates the poller and fetcher to fix a race condition in bulk requeue during shutdown [#1406] 2.17.2 ----------- - Fix bug where strictly prioritized queues might be processed out of order [#1408]. A side effect of this change is that it breaks a queue declaration syntax that worked, although only because of a bug—it was never intended to work and never supported. If you were declaring your queues as a comma-separated list, e.g. `sidekiq -q critical,default,low`, you must now use the `-q` flag before each queue, e.g. `sidekiq -q critical -q default -q low`. 2.17.1 ----------- - Expose `delay` extension as `sidekiq_delay` also. This allows you to run Delayed::Job and Sidekiq in the same process, selectively porting `delay` calls to `sidekiq_delay`. You just need to ensure that Sidekiq is required **before** Delayed::Job in your Gemfile. [#1393] - Bump redis client required version to 3.0.6 - Minor CSS fixes for Web UI 2.17.0 ----------- - Change `Sidekiq::Client#push_bulk` to return an array of pushed `jid`s. [#1315, barelyknown] - Web UI refactoring to use more API internally (yummy dogfood!) - Much faster Sidekiq::Job#delete performance for larger queue sizes - Further capistrano 3 fixes - Many misc minor fixes 2.16.1 ----------- - Revert usage of `resolv-replace`. MRI's native DNS lookup releases the GIL. - Fix several Capistrano 3 issues - Escaping dynamic data like job args and error messages in Sidekiq Web UI. [#1299, lian] 2.16.0 ----------- - Deprecate `Sidekiq::Client.registered_workers` and `Sidekiq::Client.registered_queues` - Refactor Sidekiq::Client to be instance-based [#1279] - Pass all Redis options to the Redis driver so Unix sockets can be fully configured. [#1270, salimane] - Allow sidekiq-web extensions to add locale paths so extensions can be localized. [#1261, ondrejbartas] - Capistrano 3 support [#1254, phallstrom] - Use Ruby's `resolv-replace` to enable pure Ruby DNS lookups. This ensures that any DNS resolution that takes place in worker threads won't lock up the entire VM on MRI. [#1258] 2.15.2 ----------- - Iterating over Sidekiq::Queue and Sidekiq::SortedSet will now work as intended when jobs are deleted [#866, aackerman] - A few more minor Web UI fixes [#1247] 2.15.1 ----------- - Fix several Web UI issues with the Bootstrap 3 upgrade. 2.15.0 ----------- - The Core Sidekiq actors are now monitored. If any crash, the Sidekiq process logs the error and exits immediately. This is to help prevent "stuck" Sidekiq processes which are running but don't appear to be doing any work. [#1194] - Sidekiq's testing behavior is now dynamic. You can choose between `inline` and `fake` behavior in your tests. See [Testing](https://github.com/mperham/sidekiq/wiki/Testing) for detail. [#1193] - The Retries table has a new column for the error message. - The Web UI topbar now contains the status and live poll button. - Orphaned worker records are now auto-vacuumed when you visit the Workers page in the Web UI. - Sidekiq.default\_worker\_options allows you to configure default options for all Sidekiq worker types. ```ruby Sidekiq.default_worker_options = { 'queue' => 'default', 'backtrace' => true } ``` - Added two Sidekiq::Client class methods for compatibility with resque-scheduler: `enqueue_to_in` and `enqueue_in` [#1212] - Upgrade Web UI to Bootstrap 3.0. [#1211, jeffboek] 2.14.1 ----------- - Fix misc Web UI issues due to ERB conversion. - Bump redis-namespace version due to security issue. 2.14.0 ----------- - Removed slim gem dependency, Web UI now uses ERB [Locke23rus, #1120] - Fix more race conditions in Web UI actions - Don't reset Job enqueued\_at when retrying - Timestamp tooltips in the Web UI should use UTC - Fix invalid usage of handle\_exception causing issues in Airbrake [#1134] 2.13.1 ----------- - Make Sidekiq::Middleware::Chain Enumerable - Make summary bar and graphs responsive [manishval, #1025] - Adds a job status page for scheduled jobs [jonhyman] - Handle race condition in retrying and deleting jobs in the Web UI - The Web UI relative times are now i18n. [MadRabbit, #1088] - Allow for default number of retry attempts to be set for `Sidekiq::Middleware::Server::RetryJobs` middleware. [czarneckid] [#1091] ```ruby Sidekiq.configure_server do |config| config.server_middleware do |chain| chain.add Sidekiq::Middleware::Server::RetryJobs, :max_retries => 10 end end ``` 2.13.0 ----------- - Adding button to move scheduled job to main queue [guiceolin, #1020] - fix i18n support resetting saved locale when job is retried [#1011] - log rotation via USR2 now closes the old logger [#1008] - Add ability to customize retry schedule, like so [jmazzi, #1027] ```ruby class MyWorker include Sidekiq::Worker sidekiq_retry_in { |count| count * 2 } end ``` - Redesign Worker#retries\_exhausted callback to use same form as above [jmazzi, #1030] ```ruby class MyWorker include Sidekiq::Worker sidekiq_retries_exhausted do |msg| Rails.logger.error "Failed to process #{msg['class']} with args: #{msg['args']}" end end ``` 2.12.4 ----------- - Fix error in previous release which crashed the Manager when a Processor died. 2.12.3 ----------- - Revert back to Celluloid's TaskFiber for job processing which has proven to be more stable than TaskThread. [#985] - Avoid possible lockup during hard shutdown [#997] At this point, if you are experiencing stability issues with Sidekiq in Ruby 1.9, please try Ruby 2.0. It seems to be more stable. 2.12.2 ----------- - Relax slim version requirement to >= 1.1.0 - Refactor historical stats to use TTL, not explicit cleanup. [grosser, #971] 2.12.1 ----------- - Force Celluloid 0.14.1 as 0.14.0 has a serious bug. [#954] - Scheduled and Retry jobs now use Sidekiq::Client to push jobs onto the queue, so they use client middleware. [dimko, #948] - Record the timestamp when jobs are enqueued. Add Sidekiq::Job#enqueued\_at to query the time. [mariovisic, #944] - Add Sidekiq::Queue#latency - calculates diff between now and enqueued\_at for the oldest job in the queue. - Add testing method `perform_one` that dequeues and performs a single job. This is mainly to aid testing jobs that spawn other jobs. [fumin, #963] 2.12.0 ----------- - Upgrade to Celluloid 0.14, remove the use of Celluloid's thread pool. This should halve the number of threads in each Sidekiq process, thus requiring less resources. [#919] - Abstract Celluloid usage to Sidekiq::Actor for testing purposes. - Better handling for Redis downtime when fetching jobs and shutting down, don't print exceptions every second and print success message when Redis is back. - Fix unclean shutdown leading to duplicate jobs [#897] - Add Korean locale [#890] - Upgrade test suite to Minitest 5 - Remove usage of `multi_json` as `json` is now robust on all platforms. 2.11.2 ----------- - Fix Web UI when used without Rails [#886] - Add Sidekiq::Stats#reset [#349] - Add Norwegian locale. - Updates for the JA locale. 2.11.1 ----------- - Fix timeout warning. - Add Dutch web UI locale. 2.11.0 ----------- - Upgrade to Celluloid 0.13. [#834] - Remove **timeout** support from `sidekiq_options`. Ruby's timeout is inherently unsafe in a multi-threaded application and was causing stability problems for many. See http://bit.ly/OtYpK - Add Japanese locale for Web UI [#868] - Fix a few issues with Web UI i18n. 2.10.1 ----------- - Remove need for the i18n gem. (brandonhilkert) - Improve redis connection info logging on startup for debugging purposes [#858] - Revert sinatra/slim as runtime dependencies - Add `find_job` method to sidekiq/api 2.10.0 ----------- - Refactor algorithm for putting scheduled jobs onto the queue [#843] - Fix scheduler thread dying due to incorrect error handling [#839] - Fix issue which left stale workers if Sidekiq wasn't shutdown while quiet. [#840] - I18n for web UI. Please submit translations of `web/locales/en.yml` for your own language. [#811] - 'sinatra', 'slim' and 'i18n' are now gem dependencies for Sidekiq. 2.9.0 ----------- - Update 'sidekiq/testing' to work with any Sidekiq::Client call. It also serializes the arguments as using Redis would. [#713] - Raise a Sidekiq::Shutdown error within workers which don't finish within the hard timeout. This is to prevent unwanted database transaction commits. [#377] - Lazy load Redis connection pool, you no longer need to specify anything in Passenger or Unicorn's after_fork callback [#794] - Add optional Worker#retries_exhausted hook after max retries failed. [jkassemi, #780] - Fix bug in pagination link to last page [pitr, #774] - Upstart scripts for multiple Sidekiq instances [dariocravero, #763] - Use select via pipes instead of poll to catch signals [mrnugget, #761] 2.8.0 ----------- - I18n support! Sidekiq can optionally save and restore the Rails locale so it will be properly set when your jobs execute. Just include `require 'sidekiq/middleware/i18n'` in your sidekiq initializer. [#750] - Fix bug which could lose messages when using namespaces and the message needs to be requeued in Redis. [#744] - Refactor Redis namespace support [#747]. The redis namespace can no longer be passed via the config file, the only supported way is via Ruby in your initializer: ```ruby sidekiq_redis = { :url => 'redis://localhost:3679', :namespace => 'foo' } Sidekiq.configure_server { |config| config.redis = sidekiq_redis } Sidekiq.configure_client { |config| config.redis = sidekiq_redis } ``` A warning is printed out to the log if a namespace is found in your sidekiq.yml. 2.7.5 ----------- - Capistrano no longer uses daemonization in order to work with JRuby [#719] - Refactor signal handling to work on Ruby 2.0 [#728, #730] - Fix dashboard refresh URL [#732] 2.7.4 ----------- - Fixed daemonization, was broken by some internal refactoring in 2.7.3 [#727] 2.7.3 ----------- - Real-time dashboard is now the default web page - Make config file optional for capistrano - Fix Retry All button in the Web UI 2.7.2 ----------- - Remove gem signing infrastructure. It was causing Sidekiq to break when used via git in Bundler. This is why we can't have nice things. [#688] 2.7.1 ----------- - Fix issue with hard shutdown [#680] 2.7.0 ----------- - Add -d daemonize flag, capistrano recipe has been updated to use it [#662] - Support profiling via `ruby-prof` with -p. When Sidekiq is stopped via Ctrl-C, it will output `profile.html`. You must add `gem 'ruby-prof'` to your Gemfile for it to work. - Dynamically update Redis stats on dashboard [brandonhilkert] - Add Sidekiq::Workers API giving programmatic access to the current set of active workers. ``` workers = Sidekiq::Workers.new workers.size => 2 workers.each do |name, work| # name is a unique identifier per Processor instance # work is a Hash which looks like: # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg } end ``` - Allow environment-specific sections within the config file which override the global values [dtaniwaki, #630] ``` --- :concurrency: 50 :verbose: false staging: :verbose: true :concurrency: 5 ``` 2.6.5 ----------- - Several reliability fixes for job requeueing upon termination [apinstein, #622, #624] - Fix typo in capistrano recipe - Add `retry_queue` option so retries can be given lower priority [ryanlower, #620] ```ruby sidekiq_options queue: 'high', retry_queue: 'low' ``` 2.6.4 ----------- - Fix crash upon empty queue [#612] 2.6.3 ----------- - sidekiqctl exits with non-zero exit code upon error [jmazzi] - better argument validation in Sidekiq::Client [karlfreeman] 2.6.2 ----------- - Add Dashboard beacon indicating when stats are updated. [brandonhilkert, #606] - Revert issue with capistrano restart. [#598] 2.6.1 ----------- - Dashboard now live updates summary stats also. [brandonhilkert, #605] - Add middleware chain APIs `insert_before` and `insert_after` for fine tuning the order of middleware. [jackrg, #595] 2.6.0 ----------- - Web UI much more mobile friendly now [brandonhilkert, #573] - Enable live polling for every section in Web UI [brandonhilkert, #567] - Add Stats API [brandonhilkert, #565] - Add Stats::History API [brandonhilkert, #570] - Add Dashboard to Web UI with live and historical stat graphs [brandonhilkert, #580] - Add option to log output to a file, reopen log file on USR2 signal [mrnugget, #581] 2.5.4 ----------- - `Sidekiq::Client.push` now accepts the worker class as a string so the Sidekiq client does not have to load your worker classes at all. [#524] - `Sidekiq::Client.push_bulk` now works with inline testing. - **Really** fix status icon in Web UI this time. - Add "Delete All" and "Retry All" buttons to Retries in Web UI 2.5.3 ----------- - Small Web UI fixes - Add `delay_until` so you can delay jobs until a specific timestamp: ```ruby Auction.delay_until(@auction.ends_at).close(@auction.id) ``` This is identical to the existing Sidekiq::Worker method, `perform_at`. 2.5.2 ----------- - Remove asset pipeline from Web UI for much faster, simpler runtime. [#499, #490, #481] - Add -g option so the procline better identifies a Sidekiq process, defaults to File.basename(Rails.root). [#486] sidekiq 2.5.1 myapp [0 of 25 busy] - Add splay to retry time so groups of failed jobs don't fire all at once. [#483] 2.5.1 ----------- - Fix issues with core\_ext 2.5.0 ----------- - REDESIGNED WEB UI! [unity, cavneb] - Support Honeybadger for error delivery - Inline testing runs the client middleware before executing jobs [#465] - Web UI can now remove jobs from queue. [#466, dleung] - Web UI can now show the full message, not just 100 chars [#464, dleung] - Add APIs for manipulating the retry and job queues. See sidekiq/api. [#457] 2.4.0 ----------- - ActionMailer.delay.method now only tries to deliver if method returns a valid message. - Logging now uses "MSG-#{Job ID}", not a random msg ID - Allow generic Redis provider as environment variable. [#443] - Add ability to customize sidekiq\_options with delay calls [#450] ```ruby Foo.delay(:retry => false).bar Foo.delay(:retry => 10).bar Foo.delay(:timeout => 10.seconds).bar Foo.delay_for(5.minutes, :timeout => 10.seconds).bar ``` 2.3.3 ----------- - Remove option to disable Rails hooks. [#401] - Allow delay of any module class method 2.3.2 ----------- - Fix retry. 2.3.1 accidentally disabled it. 2.3.1 ----------- - Add Sidekiq::Client.push\_bulk for bulk adding of jobs to Redis. My own simple test case shows pushing 10,000 jobs goes from 5 sec to 1.5 sec. - Add support for multiple processes per host to Capistrano recipe - Re-enable Celluloid::Actor#defer to fix stack overflow issues [#398] 2.3.0 ----------- - Upgrade Celluloid to 0.12 - Upgrade Twitter Bootstrap to 2.1.0 - Rescue more Exceptions - Change Job ID to be Hex, rather than Base64, for HTTP safety - Use `Airbrake#notify_or_ignore` 2.2.1 ----------- - Add support for custom tabs to Sidekiq::Web [#346] - Change capistrano recipe to run 'quiet' before deploy:update\_code so it is run upon both 'deploy' and 'deploy:migrations'. [#352] - Rescue Exception rather than StandardError to catch and log any sort of Processor death. 2.2.0 ----------- - Roll back Celluloid optimizations in 2.1.0 which caused instability. - Add extension to delay any arbitrary class method to Sidekiq. Previously this was limited to ActiveRecord classes. ```ruby SomeClass.delay.class_method(1, 'mike', Date.today) ``` - Sidekiq::Client now generates and returns a random, 128-bit Job ID 'jid' which can be used to track the processing of a Job, e.g. for calling back to a webhook when a job is finished. 2.1.1 ----------- - Handle networking errors causing the scheduler thread to die [#309] - Rework exception handling to log all Processor and actor death (#325, subelsky) - Clone arguments when calling worker so modifications are discarded. (#265, hakanensari) 2.1.0 ----------- - Tune Celluloid to no longer run message processing within a Fiber. This gives us a full Thread stack and also lowers Sidekiq's memory usage. - Add pagination within the Web UI [#253] - Specify which Redis driver to use: *hiredis* or *ruby* (default) - Remove FailureJobs and UniqueJobs, which were optional middleware that I don't want to support in core. [#302] 2.0.3 ----------- - Fix sidekiq-web's navbar on mobile devices and windows under 980px (ezkl) - Fix Capistrano task for first deploys [#259] - Worker subclasses now properly inherit sidekiq\_options set in their superclass [#221] - Add random jitter to scheduler to spread polls across POLL\_INTERVAL window. [#247] - Sidekiq has a new mailing list: sidekiq@librelist.org See README. 2.0.2 ----------- - Fix "Retry Now" button on individual retry page. (ezkl) 2.0.1 ----------- - Add "Clear Workers" button to UI. If you kill -9 Sidekiq, the workers set can fill up with stale entries. - Update sidekiq/testing to support new scheduled jobs API: ```ruby require 'sidekiq/testing' DirectWorker.perform_in(10.seconds, 1, 2) assert_equal 1, DirectWorker.jobs.size assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.01 ``` 2.0.0 ----------- - **SCHEDULED JOBS**! You can now use `perform_at` and `perform_in` to schedule jobs to run at arbitrary points in the future, like so: ```ruby SomeWorker.perform_in(5.days, 'bob', 13) SomeWorker.perform_at(5.days.from_now, 'bob', 13) ``` It also works with the delay extensions: ```ruby UserMailer.delay_for(5.days).send_welcome_email(user.id) ``` The time is approximately when the job will be placed on the queue; it is not guaranteed to run at precisely at that moment in time. This functionality is meant for one-off, arbitrary jobs. I still recommend `whenever` or `clockwork` if you want cron-like, recurring jobs. See `examples/scheduling.rb` I want to specially thank @yabawock for his work on sidekiq-scheduler. His extension for Sidekiq 1.x filled an obvious functional gap that I now think is useful enough to implement in Sidekiq proper. - Fixed issues due to Redis 3.x API changes. Sidekiq now requires the Redis 3.x client. - Inline testing now round trips arguments through JSON to catch serialization issues (betelgeuse) 1.2.1 ----------- - Sidekiq::Worker now has access to Sidekiq's standard logger - Fix issue with non-StandardErrors leading to Processor exhaustion - Fix issue with Fetcher slowing Sidekiq shutdown - Print backtraces for all threads upon TTIN signal [#183] - Overhaul retries Web UI with new index page and bulk operations [#184] 1.2.0 ----------- - Full or partial error backtraces can optionally be stored as part of the retry for display in the web UI if you aren't using an error service. [#155] ```ruby class Worker include Sidekiq::Worker sidekiq_options :backtrace => [true || 10] end ``` - Add timeout option to kill a worker after N seconds (blackgold9) ```ruby class HangingWorker include Sidekiq::Worker sidekiq_options :timeout => 600 def perform # will be killed if it takes longer than 10 minutes end end ``` - Fix delayed extensions not available in workers [#152] - In test environments add the `#drain` class method to workers. This method executes all previously queued jobs. (panthomakos) - Sidekiq workers can be run inline during tests, just `require 'sidekiq/testing/inline'` (panthomakos) - Queues can now be deleted from the Sidekiq web UI [#154] - Fix unnecessary shutdown delay due to Retry Poller [#174] 1.1.4 ----------- - Add 24 hr expiry for basic keys set in Redis, to avoid any possible leaking. - Only register workers in Redis while working, to avoid lingering workers [#156] - Speed up shutdown significantly. 1.1.3 ----------- - Better network error handling when fetching jobs from Redis. Sidekiq will retry once per second until it can re-establish a connection. (ryanlecompte) - capistrano recipe now uses `bundle_cmd` if set [#147] - handle multi\_json API changes (sferik) 1.1.2 ----------- - Fix double restart with cap deploy [#137] 1.1.1 ----------- - Set procline for easy monitoring of Sidekiq status via "ps aux" - Fix race condition on shutdown [#134] - Fix hang with cap sidekiq:start [#131] 1.1.0 ----------- - The Sidekiq license has switched from GPLv3 to LGPLv3! - Sidekiq::Client.push now returns whether the actual Redis operation succeeded or not. [#123] - Remove UniqueJobs from the default middleware chain. Its functionality, while useful, is unexpected for new Sidekiq users. You can re-enable it with the following config. Read #119 for more discussion. ```ruby Sidekiq.configure_client do |config| require 'sidekiq/middleware/client/unique_jobs' config.client_middleware do |chain| chain.add Sidekiq::Middleware::Client::UniqueJobs end end Sidekiq.configure_server do |config| require 'sidekiq/middleware/server/unique_jobs' config.server_middleware do |chain| chain.add Sidekiq::Middleware::Server::UniqueJobs end end ``` 1.0.0 ----------- Thanks to all Sidekiq users and contributors for helping me get to this big milestone! - Default concurrency on client-side to 5, not 25 so we don't create as many unused Redis connections, same as ActiveRecord's default pool size. - Ensure redis= is given a Hash or ConnectionPool. 0.11.2 ----------- - Implement "safe shutdown". The messages for any workers that are still busy when we hit the TERM timeout will be requeued in Redis so the messages are not lost when the Sidekiq process exits. [#110] - Work around Celluloid's small 4kb stack limit [#115] - Add support for a custom Capistrano role to limit Sidekiq to a set of machines. [#113] 0.11.1 ----------- - Fix fetch breaking retry when used with Redis namespaces. [#109] - Redis connection now just a plain ConnectionPool, not CP::Wrapper. - Capistrano initial deploy fix [#106] - Re-implemented weighted queues support (ryanlecompte) 0.11.0 ----------- - Client-side API changes, added sidekiq\_options for Sidekiq::Worker. As a side effect of this change, the client API works on Ruby 1.8. It's not officially supported but should work [#103] - NO POLL! Sidekiq no longer polls Redis, leading to lower network utilization and lower latency for message processing. - Add --version CLI option 0.10.1 ----------- - Add details page for jobs in retry queue (jcoene) - Display relative timestamps in web interface (jcoene) - Capistrano fixes (hinrik, bensie) 0.10.0 ----------- - Reworked capistrano recipe to make it more fault-tolerant [#94]. - Automatic failure retry! Sidekiq will now save failed messages and retry them, with an exponential backoff, over about 20 days. Did a message fail to process? Just deploy a bug fix in the next few days and Sidekiq will retry the message eventually. 0.9.1 ----------- - Fix missed deprecations, poor method name in web UI 0.9.0 ----------- - Add -t option to configure the TERM shutdown timeout - TERM shutdown timeout is now configurable, defaults to 5 seconds. - USR1 signal now stops Sidekiq from accepting new work, capistrano sends USR1 at start of deploy and TERM at end of deploy giving workers the maximum amount of time to finish. - New Sidekiq::Web rack application available - Updated Sidekiq.redis API 0.8.0 ----------- - Remove :namespace and :server CLI options (mperham) - Add ExceptionNotifier support (masterkain) - Add capistrano support (mperham) - Workers now log upon start and finish (mperham) - Messages for terminated workers are now automatically requeued (mperham) - Add support for Exceptional error reporting (bensie) 0.7.0 ----------- - Example chef recipe and monitrc script (jc00ke) - Refactor global configuration into Sidekiq.configure\_server and Sidekiq.configure\_client blocks. (mperham) - Add optional middleware FailureJobs which saves failed jobs to a 'failed' queue (fbjork) - Upon shutdown, workers are now terminated after 5 seconds. This is to meet Heroku's hard limit of 10 seconds for a process to shutdown. (mperham) - Refactor middleware API for simplicity, see sidekiq/middleware/chain. (mperham) - Add `delay` extensions for ActionMailer and ActiveRecord. (mperham) - Added config file support. See test/config.yml for an example file. (jc00ke) - Added pidfile for tools like monit (jc00ke) 0.6.0 ----------- - Resque-compatible processing stats in redis (mperham) - Simple client testing support in sidekiq/testing (mperham) - Plain old Ruby support via the -r cli flag (mperham) - Refactored middleware support, introducing ability to add client-side middleware (ryanlecompte) - Added middleware for ignoring duplicate jobs (ryanlecompte) - Added middleware for displaying jobs in resque-web dashboard (maxjustus) - Added redis namespacing support (maxjustus) 0.5.1 ----------- - Initial release! sidekiq-4.0.1/Ent-Changes.md0000644000175600017570000000260612631157272014636 0ustar pravipraviSidekiq Enterprise Changelog ======================= Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy. 1.0.0.pre1 ---------- - Enterprise 1.x targets Sidekiq 4.x. - Rewrite several features to remove Celluloid dependency. No functional changes. 0.7.8 ---------- - Fix `unique_for: false` [#2658] 0.7.7 ---------- - Enterprise 0.x targets Sidekiq 3.x. - Fix racy shutdown event which could lead to disappearing periodic jobs, requires Sidekiq >= 3.5.3. - Add new :leader event which is fired when a process gains leadership. 0.7.6 ---------- - Redesign how overrated jobs are rescheduled to avoid creating new jobs. [#2619] 0.7.5 ---------- - Fix dynamic creation of concurrent limiters [#2617] 0.7.4 ---------- - Add additional check to prevent duplicate periodic job creation - Allow user-specified TTLs for rate limiters [#2607] - Paginate rate limiter index page [#2606] 0.7.3 ---------- - Rework `Sidekiq::Limiter` redis handling to match global redis handling. - Allow user to customize rate limit backoff logic and handle custom rate limit errors. - Fix scalability issue with Limiter index page. 0.7.2 ---------- - Fix typo which prevented limiters with '0' in their names. 0.7.1 ---------- - Fix issue where unique scheduled jobs can't be enqueued upon schedule due to the existing unique lock. [#2499] 0.7.0 ---------- Initial release.