Cleaning up unmanaged files in boxen / puppet

Nginx and env files .. maybe others … get a mess after a while of trying different branch, since they are never auto-cleaned up. Already made this into a PR, but nobody seems to care … so fix it via overwrites.

Add an empty directory as modules/ours/files/emptydir + .gitkeep (pick whatever you want for ‘ours’).

  # remove any unmanaged configs
  # https://github.com/boxen/puppet-nginx/pull/34
  File <| title == $nginx::config::configdir |> {
    purge => true,
    force => true,
    source => "puppet:///modules/ours/emptydir",
    recurse => "true",
  }

  File <| title == $nginx::config::sitesdir |> {
    purge => true,
    force => true,
    source => "puppet:///modules/ours/emptydir",
    recurse => "true",
  }

  File <| title == $boxen::config::envdir |> {
    purge => true,
    force => true,
    source => "puppet:///modules/ours/emptydir",
    recurse => "true",
  }

Stop rails from swallowing after_commit exceptions

Problem
By default rails just swallows any exception raised in after_commit blocks.

Solution
Send these exceptions to and exception service to get notified (Airbrake / Rollbar etc).

Gem
https://github.com/grosser/after_commit_exception_notification

Copy-paste

module Foo
  module CommittedWithExceptions
    def committed!
      super
    rescue Exception => e # same as active_record/connection_adapters/abstract/database_statements.rb:370
      ExceptionService.report("after_commit exception", e)
      raise
    end
  end
end

ActiveRecord::Base.include Foo::CommittedWithExceptions

Testing multiple Gemfiles and Rubies with WWTD (no Appraisal)

Problem
Testing against many gemfiles/rubies is painful, Appraisal offers a somewhat working solution but adds new steps/problems.

Solution
Use WWTD by using the gemspec for dependencies or a gemfiles/common.rb
(Also no longer runs gemfiles that are excluded in travis.yml)

Example PR

Usage

  • Run tests on all gemfiles: rake
  • Run tests on all gemfiles and all rubies: rake wwtd
  • Run tests on Gemfile: rake test

Code

# Gemfile
source "https://rubygems.org"
gemspec
gem "rails" # newest

# Rakefile
require 'wwtd/tasks'
task default: "wwtd:local"

# gemfiles/rails32.gemfile
source "https://rubygems.org"
gemspec path: "../"
gem "rails", "~> 3.2.18"

# gemfiles/rails40.gemfile
source "https://rubygems.org"
gemspec path: "../"
gem "rails", "~> 4.0.6"

# foo.gemspec
...
s.add_runtime_dependency "rails", ">= 3.2.18"

# .travis.yml
rvm:
  - 1.9.3
  - 2.0.0
  - 2.1.2
gemfile:
  - gemfiles/rails32.gemfile
  - gemfiles/rails40.gemfile
script: "rake test"

Git dependencies
Cannot be specified in gemspec, so we put them in a loadable file.

# all gemfiles
eval(File.read('gemfiles/common.rb'))

# gemfiles/common.rb
gem "foo", git: "https://github.com/bar/foo"

Locking insights: an alternative to redis set nx ex / memcache add

A lock that does not timeout can lead to a standstill and manual cleanup. Simple solution: redis ‘set ex nx’ and memcached ‘add’.

When indefinite locks happen, getting information on why they happen helps to debug the locking mechanism and see if the processes always fail to unlock.

A softer locking approach to receive feedback when locks expire:

Code

def lock
  timeout = 30
  key = 'lock'
  now = Time.now.to_i
  if redis.setnx(key, "#{now}-#{Process.pid}-#{Socket.gethostname}")
    yield
  elsif (old = redis.get(key)) && now > old.to_i + timeout
    logger.error("Releasing expired lock #{old}")
    redis.delete(key) # next process can get the lock 
  end
end

Not 100% safe since the delete could cause multiple processes to get a lock, but depending on your usecase this might be an ok tradeoff.