aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/etherpad/src/etherpad/pad/pad_migrations.js
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/etherpad/src/etherpad/pad/pad_migrations.js')
-rw-r--r--trunk/etherpad/src/etherpad/pad/pad_migrations.js206
1 files changed, 206 insertions, 0 deletions
diff --git a/trunk/etherpad/src/etherpad/pad/pad_migrations.js b/trunk/etherpad/src/etherpad/pad/pad_migrations.js
new file mode 100644
index 0000000..e81cf63
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/pad/pad_migrations.js
@@ -0,0 +1,206 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("sqlbase.sqlobj");
+import("etherpad.pad.model");
+import("etherpad.pad.easysync2migration");
+import("sqlbase.sqlcommon");
+import("sqlbase.sqlobj");
+import("etherpad.log");
+import("etherpad.pne.pne_utils");
+jimport("java.util.concurrent.ConcurrentHashMap");
+jimport("java.lang.System");
+jimport("java.util.ArrayList");
+jimport("java.util.Collections");
+
+function onStartup() {
+ if (! appjet.cache.pad_migrations) {
+ appjet.cache.pad_migrations = {};
+ }
+
+ // this part can be removed when all pads are migrated on pad.spline.inf.fu-berlin.de
+ //if (! pne_utils.isPNE()) {
+ // System.out.println("Building cache for live migrations...");
+ // initLiveMigration();
+ //}
+}
+
+function initLiveMigration() {
+
+ if (! appjet.cache.pad_migrations) {
+ appjet.cache.pad_migrations = {};
+ }
+ appjet.cache.pad_migrations.doingAnyLiveMigrations = true;
+ appjet.cache.pad_migrations.doingBackgroundLiveMigrations = true;
+ appjet.cache.pad_migrations.padMap = new ConcurrentHashMap();
+
+ // presence of a pad in padMap indicates migration is needed
+ var padMap = _padMap();
+ var migrationsNeeded = sqlobj.selectMulti("PAD_SQLMETA", {version: 1});
+ migrationsNeeded.forEach(function(obj) {
+ padMap.put(String(obj.id), {from: obj.version});
+ });
+}
+
+function _padMap() {
+ return appjet.cache.pad_migrations.padMap;
+}
+
+function _doingItLive() {
+ return !! appjet.cache.pad_migrations.doingAnyLiveMigrations;
+}
+
+function checkPadStatus(padId) {
+ if (! _doingItLive()) {
+ return "ready";
+ }
+ var info = _padMap().get(padId);
+ if (! info) {
+ return "ready";
+ }
+ else if (info.migrating) {
+ return "migrating";
+ }
+ else {
+ return "oldversion";
+ }
+}
+
+function ensureMigrated(padId, async) {
+ if (! _doingItLive()) {
+ return false;
+ }
+
+ var info = _padMap().get(padId);
+ if (! info) {
+ // pad is up-to-date
+ return false;
+ }
+ else if (async && info.migrating) {
+ // pad is already being migrated, don't wait on the lock
+ return false;
+ }
+
+ return model.doWithPadLock(padId, function() {
+ // inside pad lock...
+ var info = _padMap().get(padId);
+ if (!info) {
+ return false;
+ }
+ // migrate from version 1 to version 2 in a transaction
+ var migrateSucceeded = false;
+ try {
+ info.migrating = true;
+ log.info("Migrating pad "+padId+" from version 1 to version 2...");
+
+ var success = false;
+ var whichTry = 1;
+ while ((! success) && whichTry <= 3) {
+ success = sqlcommon.inTransaction(function() {
+ try {
+ easysync2migration.migratePad(padId);
+ sqlobj.update("PAD_SQLMETA", {id: padId}, {version: 2});
+ return true;
+ }
+ catch (e if (e.toString().indexOf("try restarting transaction") >= 0)) {
+ whichTry++;
+ return false;
+ }
+ });
+ if (! success) {
+ java.lang.Thread.sleep(Math.floor(Math.random()*200));
+ }
+ }
+ if (! success) {
+ throw new Error("too many retries");
+ }
+
+ migrateSucceeded = true;
+ log.info("Migrated pad "+padId+".");
+ _padMap().remove(padId);
+ }
+ finally {
+ info.migrating = false;
+ if (! migrateSucceeded) {
+ log.info("Migration failed for pad "+padId+".");
+ throw new Error("Migration failed for pad "+padId+".");
+ }
+ }
+ return true;
+ });
+}
+
+function numUnmigratedPads() {
+ if (! _doingItLive()) {
+ return 0;
+ }
+
+ return _padMap().size();
+}
+
+////////// BACKGROUND MIGRATIONS
+
+function _logPadMigration(runnerId, padNumber, padTotal, timeMs, fourCharResult, padId) {
+ log.custom("pad_migrations", {
+ runnerId: runnerId,
+ padNumber: Math.round(padNumber+1),
+ padTotal: Math.round(padTotal),
+ timeMs: Math.round(timeMs),
+ fourCharResult: fourCharResult,
+ padId: padId});
+}
+
+function _getNeededMigrationsArrayList(filter) {
+ var L = new ArrayList(_padMap().keySet());
+ for(var i=L.size()-1; i>=0; i--) {
+ if (! filter(String(L.get(i)))) {
+ L.remove(i);
+ }
+ }
+ return L;
+}
+
+function runBackgroundMigration(residue, modulus, runnerId) {
+ var L = _getNeededMigrationsArrayList(function(padId) {
+ return (padId.charCodeAt(0) % modulus) == residue;
+ });
+ Collections.shuffle(L);
+
+ var totalPads = L.size();
+ for(var i=0;i<totalPads;i++) {
+ if (! appjet.cache.pad_migrations.doingBackgroundLiveMigrations) {
+ break;
+ }
+ var padId = L.get(i);
+ var result = "FAIL";
+ var t1 = System.currentTimeMillis();
+ try {
+ if (ensureMigrated(padId, true)) {
+ result = " OK "; // migrated successfully
+ }
+ else {
+ result = " -- "; // no migration needed after all
+ }
+ }
+ catch (e) {
+ // e just says "migration failed", but presumably
+ // inTransaction() printed a stack trace.
+ // result == "FAIL", do nothing.
+ }
+ var t2 = System.currentTimeMillis();
+ _logPadMigration(runnerId, i, totalPads, t2 - t1, result, padId);
+ }
+}