From 9d68773b05a767ce53f316b966e1dc9b73c93a8a Mon Sep 17 00:00:00 2001 From: Jan Sandbrink Date: Tue, 15 Sep 2015 11:06:58 +0200 Subject: [PATCH] Use an advisory lock that is released on COMMIT/ROLLBACK for MySQL this seems to be the only available workaround that is not destroying running transactions. Note that TransactionalLock is a self-developed gem, that will emulate PostgreSQL style advisory locks, that are automatically released at transaction end. --- Gemfile | 2 ++ Gemfile.lock | 8 ++++++ app/models/journal.rb | 13 +++++---- config/initializers/transactional_lock.rb | 34 +++++++++++++++++++++++ 4 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 config/initializers/transactional_lock.rb diff --git a/Gemfile b/Gemfile index 33403f3c0b..df7415c301 100644 --- a/Gemfile +++ b/Gemfile @@ -99,6 +99,8 @@ gem 'gon', '~> 4.0' # don't require by default, instead load on-demand when actually configured gem 'airbrake', '~> 4.1.0', require: false +gem 'transactional_lock', git: 'https://github.com/finnlabs/transactional_lock.git', branch: 'master' + group :production do # we use dalli as standard memcache client # requires memcached 1.4+ diff --git a/Gemfile.lock b/Gemfile.lock index 1d383c2821..0c5c0787f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,6 +28,13 @@ GIT specs: rspec-example_disabler (0.0.1) +GIT + remote: https://github.com/finnlabs/transactional_lock.git + revision: fe82192efa0346e49d19bbe82865280fdae79487 + branch: master + specs: + transactional_lock (0.1.0) + GIT remote: https://github.com/opf/openproject-translations.git revision: a2a7d246c6212c0867970ee44c505f97e5c96ee3 @@ -596,6 +603,7 @@ DEPENDENCIES sys-filesystem (~> 1.1.4) thin timecop (~> 0.7.1) + transactional_lock! unicorn warden (~> 1.2) warden-basic_auth (~> 0.2.1) diff --git a/app/models/journal.rb b/app/models/journal.rb index 919e02c9d8..38642608d5 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -63,11 +63,14 @@ class Journal < ActiveRecord::Base # Note for MySQL: THis method does not currently change anything (no locking at all) def self.with_write_lock if OpenProject::Database.mysql? - # N.B. there seems to be no acceptable locking available for MySQL: - # - table locks will break (i.e. COMMIT) an already running transaction. - # - advisory locks will do their job, except that when they end inside a transaction, - # nobody will see the changes from inside the lock until the transaction finished. - yield + Journal.transaction do + # MySQL is very weak when combining transactions and locks. Using an emulation layer to + # automatically release an advisory lock at the end of the transaction + # FIXME: still creates duplicates... because MySQL defaults to READ REPEATABLE isolation + # we need READ COMMITED, which is also the default for PostgreSQL + TransactionalLock::AdvisoryLock.new('journals.write_lock').acquire + yield + end else Journal.transaction do ActiveRecord::Base.connection.execute("LOCK TABLE #{table_name} IN SHARE ROW EXCLUSIVE MODE") diff --git a/config/initializers/transactional_lock.rb b/config/initializers/transactional_lock.rb new file mode 100644 index 0000000000..9eb2b7b49a --- /dev/null +++ b/config/initializers/transactional_lock.rb @@ -0,0 +1,34 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +OpenProject::Application.configure do + config.after_initialize do + TransactionalLock.initialize + end +end