You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by oy...@apache.org on 2008/01/14 11:54:35 UTC

svn commit: r611759 - in /db/derby/code/trunk/java/engine/org/apache/derby: ./ iapi/services/replication/slave/ impl/db/ impl/services/monitor/ impl/store/raw/log/

Author: oysteing
Date: Mon Jan 14 02:54:33 2008
New Revision: 611759

URL: http://svn.apache.org/viewvc?rev=611759&view=rev
Log:
DERBY-3184: Replication: Connection attempts to a database in slave mode must fail (Contributed by Jorgen Lovland)
The attached patch, v2a, adds a new implementation of Database to Derby. The new implementation is called SlaveDatabase and is booted if startSlave=true is specified in the connection url. The following files are modified:

A java/engine/org/apache/derby/impl/db/SlaveDatabase.java
M java/engine/org/apache/derby/modules.properties

The new Database implementation "SlaveDatabase" to Derby.

M java/engine/org/apache/derby/impl/db/BasicDatabase.java

Removed the old "if (inReplicationSlaveMode) throw exception" code. This is now handled in SlaveDatabase.

M java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java

Adds slave replication pre mode

M java/engine/org/apache/derby/iapi/services/replication/slave/SlaveFactory.java

Minor changes

M java/engine/org/apache/derby/impl/services/monitor/UpdateServiceProperties.java

Make StorageFactory volatile to make sure concurrent threads detect that it is set.

Added:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/db/SlaveDatabase.java   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/replication/slave/SlaveFactory.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/UpdateServiceProperties.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java
    db/derby/code/trunk/java/engine/org/apache/derby/modules.properties

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/replication/slave/SlaveFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/replication/slave/SlaveFactory.java?rev=611759&r1=611758&r2=611759&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/replication/slave/SlaveFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/replication/slave/SlaveFactory.java Mon Jan 14 02:54:33 2008
@@ -23,7 +23,6 @@
 package org.apache.derby.iapi.services.replication.slave;
 
 import org.apache.derby.iapi.error.StandardException;
-import org.apache.derby.iapi.reference.Property;
 
 import org.apache.derby.iapi.store.raw.RawStoreFactory;
 import org.apache.derby.iapi.store.raw.log.LogFactory;
@@ -51,11 +50,11 @@
 
     /** Property key to specify the name of the database */
     public static final String SLAVE_DB =
-        Property.PROPERTY_RUNTIME_PREFIX + "replication.slave.dbname";
+        "replication.slave.dbname";
 
     /** Property key to specify replication mode */
     public static final String REPLICATION_MODE =
-        Property.PROPERTY_RUNTIME_PREFIX + "replication.slave.mode";
+        "replication.slave.mode";
 
     /* Strings used as values in the Properties objects */
 
@@ -64,8 +63,21 @@
      * booted in asynchronous replication mode.
      */
     public static final String SLAVE_MODE =
-        Property.PROPERTY_RUNTIME_PREFIX + "slavemode";
+        "slavemode";
 
+    /**
+     * Property value used to indicate that the service should be
+     * booted in slave replication pre mode. The reason for having a
+     * slave pre mode is that when slave replication is started, we
+     * need to boot the database twice: Once to check authentication
+     * and authorization, and a second time to put the database in
+     * slave mode. It is imperative that the disk image of log files
+     * remain unmodified by the first boot since the master and slave
+     * log files have to be identical when slave mode starts. Booting
+     * in SLAVE_PRE_MODE ensures that the log files remain unmodified.
+     */
+    public static final String SLAVE_PRE_MODE =
+        "slavepremode";
 
     /* Required methods */
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java?rev=611759&r1=611758&r2=611759&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/db/BasicDatabase.java Mon Jan 14 02:54:33 2008
@@ -111,7 +111,7 @@
 
 public class BasicDatabase implements ModuleControl, ModuleSupportable, PropertySetCallback, Database, JarReader
 {
-	private boolean		active;
+	protected boolean	active;
 	private AuthenticationService authenticationService;
 	protected AccessFactory af;
 	protected PropertyFactory pf;
@@ -132,7 +132,6 @@
 	private DateFormat timeFormat;
 	private DateFormat timestampFormat;
 	private UUID		myUUID;
-    private boolean inReplicationSlaveMode = false;
 
 	protected boolean lastToBoot; // is this class last to boot
 
@@ -141,20 +140,25 @@
 	 */
 
 	public boolean canSupport(Properties startParams) {
-        return Monitor.isDesiredCreateType(startParams, getEngineType());
+        boolean supported =
+            Monitor.isDesiredCreateType(startParams, getEngineType());
+
+        if (supported) {
+            String repliMode =
+                startParams.getProperty(SlaveFactory.REPLICATION_MODE);
+            if (repliMode != null &&
+                !repliMode.equals(SlaveFactory.SLAVE_PRE_MODE)) {
+                supported = false;
+            }
+        }
+
+        return supported;
 	}
 
 	public void boot(boolean create, Properties startParams)
 		throws StandardException
 	{
 
-        // Database is booted in replication slave mode. Make sure
-        // other clients are not able to connect
-        String slave = startParams.getProperty(SlaveFactory.REPLICATION_MODE);
-        if (slave != null && slave.equals(SlaveFactory.SLAVE_MODE)) {
-            inReplicationSlaveMode = true;
-        }
-
 		ModuleFactory monitor = Monitor.getMonitor();
 		if (create)
 		{
@@ -291,13 +295,6 @@
 	public LanguageConnectionContext setupConnection(ContextManager cm, String user, String drdaID, String dbname)
 		throws StandardException {
 
-        if (inReplicationSlaveMode) {
-            // do not allow connections to a database that is
-            // currently in replication slave move
-            throw StandardException.newException(
-                        SQLState.CANNOT_CONNECT_TO_DB_IN_SLAVE_MODE, dbname);
-        }
-
 		TransactionController tc = getConnectionTransaction(cm);
 
 		cm.setLocaleFinder(this);
@@ -346,7 +343,7 @@
 		DatabaseContext dc = new DatabaseContextImpl(cm, this);
 	}
 
-	public final AuthenticationService getAuthenticationService() {
+	public AuthenticationService getAuthenticationService() {
 
 		// Expected to find one - Sanity check being done at
 		// DB boot-up.

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/db/SlaveDatabase.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/db/SlaveDatabase.java?rev=611759&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/db/SlaveDatabase.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/db/SlaveDatabase.java Mon Jan 14 02:54:33 2008
@@ -0,0 +1,212 @@
+/*
+
+   Derby - Class org.apache.derby.impl.db.SlaveDatabase
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you 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.
+
+ */
+
+package org.apache.derby.impl.db;
+
+import org.apache.derby.iapi.reference.SQLState;
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.jdbc.AuthenticationService;
+import org.apache.derby.iapi.services.context.ContextManager;
+import org.apache.derby.iapi.services.context.ContextService;
+import org.apache.derby.iapi.services.monitor.Monitor;
+import org.apache.derby.iapi.services.replication.slave.SlaveFactory;
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
+import org.apache.derby.impl.services.monitor.UpdateServiceProperties;
+
+import java.util.Properties;
+
+/**
+ * SlaveDatabase is an instance of Database, and is booted instead of
+ * BasicDatabase if this database will have the replication slave
+ * role. SlaveDatabase differs from BasicDatabase in the following
+ * ways:
+ *
+ * 1: When starting a non-replicated database (i.e., BasicDatabase),
+ *    only one thread is used to start all modules of the database.
+ *    When booted in slave mode, the thread that boots the store
+ *    module will be blocked during log recovery. To remedy this,
+ *    SlaveDatabase runs the boot method of BasicDatabase in a
+ *    separate thread. This ensures that the connection attempt that
+ *    started slave replication mode will not hang.
+ *
+ * 2: While the database is in replication slave mode, the
+ *    authentication services are not available because these require
+ *    that the store module has been booted first. Calling
+ *    getAuthenticationService when in slave mode will raise an
+ *    exception.
+ *
+ * 3: While the database is in replication slave mode, connections are
+ *    not accepted since the database cannot process transaction
+ *    requests. Calling setupConnection when in slave mode will raise
+ *    an exception.
+ *
+ * 4: If the failover command has been executed for this database, it
+ *    is no longer in replication slave mode. When this has
+ *    happened, SlaveDatabase works exactly as BasicDatabase.
+ */
+
+public class SlaveDatabase extends BasicDatabase {
+    /** True until SlaveDatabaseBootThread has successfully booted the
+     * database. Does not happen until the failover command has been
+     * executed for this database */
+    private volatile boolean inReplicationSlaveMode;
+
+    /////////////////////////////
+    // ModuleControl interface //
+    /////////////////////////////
+    /**
+     * Determines whether this Database implementation should be used
+     * to boot the database.
+     * @param startParams The properties used to decide if
+     * SlaveDatabase is the correct implementation of Database for the
+     * database to be booted. 
+     * @return true if the database is updatable (not read-only) and
+     * replication slave mode is specified in startParams
+     */
+    public boolean canSupport(Properties startParams) {
+
+        boolean supported =
+            Monitor.isDesiredCreateType(startParams, getEngineType());
+        if (supported) {
+            String repliMode =
+                startParams.getProperty(SlaveFactory.REPLICATION_MODE);
+            if (repliMode == null ||
+                !repliMode.equals(SlaveFactory.SLAVE_MODE)) {
+                supported = false;
+            }
+        }
+
+        return supported;
+    }
+
+    public void boot(boolean create, Properties startParams)
+        throws StandardException {
+
+        inReplicationSlaveMode = true;
+
+        // SlaveDatabaseBootThread is an internal class
+        SlaveDatabaseBootThread dbBootThread =
+            new SlaveDatabaseBootThread(create, startParams);
+        new Thread(dbBootThread).start();
+
+        try {
+            // We cannot claim to be booted until the storage factory
+            // has been set in the startParams because
+            // TopService.bootModule (the caller of this method) uses
+            // the storage factory object. The storage factory is set
+            // in RawStore.boot, and we have to wait for this to
+            // happen.
+            UpdateServiceProperties usp =
+                (UpdateServiceProperties) startParams;
+            while (usp.getStorageFactory() == null){
+                Thread.sleep(500);
+            }
+        } catch (Exception e) {
+            //Todo: report exception to derby.log
+        }
+
+        // This module has now been booted (hence active=true) even
+        // though submodules like store and authentication may not
+        // have completed their boot yet. We deal with that by raising
+        // an error on attempts to use these 
+        active=true;
+    }
+
+    /////////////////////
+    // Class interface //
+    /////////////////////
+    public SlaveDatabase() {
+    }
+
+    ////////////////////////
+    // Database interface //
+    ////////////////////////
+    public LanguageConnectionContext setupConnection(ContextManager cm, 
+                                                     String user, 
+                                                     String drdaID, 
+                                                     String dbname)
+        throws StandardException {
+
+        if (inReplicationSlaveMode) {
+            // do not allow connections to a database that is
+            // currently in replication slave move
+            throw StandardException.newException(
+                        SQLState.CANNOT_CONNECT_TO_DB_IN_SLAVE_MODE, dbname);
+        }
+        return super.setupConnection(cm, user, drdaID, dbname);
+    }
+
+    public AuthenticationService getAuthenticationService() {
+        if (inReplicationSlaveMode) {
+            // Cannot get authentication service for a database that
+            // is currently in replication slave move
+            // Todo: throw exception
+        }
+        return super.getAuthenticationService();
+    }
+
+    /////////////////
+    // Inner Class //
+    /////////////////
+    /**
+     * Thread that boots the slave database. Will be blocked in
+     * LogFactory.recover until database is no longer in slave
+     * replication mode.
+     */
+    private class SlaveDatabaseBootThread implements Runnable {
+
+        private boolean create;
+        private Properties params;
+
+        public SlaveDatabaseBootThread(boolean create, Properties startParams){
+            this.create = create;
+            params = startParams;
+        }
+
+        public void run() {
+
+            // The thread needs a ContextManager since two threads
+            // cannot share a context
+            ContextManager bootThreadCm;
+            try {
+
+                bootThreadCm = ContextService.getFactory().newContextManager();
+                ContextService.getFactory().
+                    setCurrentContextManager(bootThreadCm);
+
+                bootBasicDatabase(create, params); // will be blocked
+
+            } catch (StandardException se) {
+                //todo - report exception
+            } finally {
+                inReplicationSlaveMode = false;
+                //todo: tear down context
+            }
+        }
+    }
+
+    private void bootBasicDatabase(boolean create, Properties params)
+        throws StandardException {
+        // This call will be blocked while slave replication mode is
+        // active
+        super.boot(create, params);
+    }
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/db/SlaveDatabase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/UpdateServiceProperties.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/UpdateServiceProperties.java?rev=611759&r1=611758&r2=611759&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/UpdateServiceProperties.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/UpdateServiceProperties.java Mon Jan 14 02:54:33 2008
@@ -38,7 +38,7 @@
 
 	private PersistentService serviceType;
 	private String serviceName;
-    private WritableStorageFactory storageFactory;
+    private volatile WritableStorageFactory storageFactory;
     
 	/*
 	Fix for bug 3668: Following would allow user to change properties while in the session

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java?rev=611759&r1=611758&r2=611759&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java Mon Jan 14 02:54:33 2008
@@ -425,6 +425,11 @@
     // initialized if this Derby has the SLAVE role for this database
     private boolean inReplicationSlaveMode = false;
 
+    /** True if the database has been booted in replication slave pre
+     * mode, effectively turning off writes to the log file.
+     * @see SlaveFactory */
+    private boolean inReplicationSlavePreMode = false;
+
     private Object slaveRecoveryMonitor; // for synchronization in slave mode
 
     // The highest log file number the recovery thread is allowed to
@@ -3012,6 +3017,8 @@
         if (mode != null && mode.equals(SlaveFactory.SLAVE_MODE)) {
             inReplicationSlaveMode = true; 
             slaveRecoveryMonitor = new Object();
+        } else if (mode != null && mode.equals(SlaveFactory.SLAVE_PRE_MODE)) {
+            inReplicationSlavePreMode = true;
         }
 
 		dataDirectory = startParams.getProperty(PersistentService.ROOT);
@@ -3621,6 +3628,13 @@
 			byte[] optionalData, int optionalDataOffset, int optionalDataLength) 
 		 throws StandardException
 	{
+        if (inReplicationSlavePreMode) {
+            // Return the *current* end of log without adding the log
+            // record to the log file. Effectively, this call to
+            // appendLogRecord does not do anything
+            return LogCounter.makeLogInstantAsLong(logFileNumber, endPosition);
+        }
+
 		long instant;
 		boolean testIncompleteLogWrite = false;
 
@@ -5039,17 +5053,6 @@
         throws StandardException {
         this.masterFactory = masterFactory;
         synchronized(this) {
-            // checkpoint followed by flushAll ensures that all data
-            // and log are written on disk. After this, the database
-            // can be safely copied to the slave location provided
-            // that no clients perform operations on the database
-            // before a connection has been established with the
-            // slave. Note: this is a hack that will be removed once
-            // the repliation functionality is able to send the
-            // database from master to slave using the network
-            // connection.
-            rawStoreFactory.checkpoint();
-            flushAll();
             inReplicationMasterMode = true;
             logOut.setReplicationMasterRole(masterFactory);
         }
@@ -5082,6 +5085,9 @@
     }
 
     /**
+     * Initializes logOut so that log received from the replication
+     * master can be appended to the log file.
+     *
      * Normally, logOut (the file log records are appended to) is set
      * up as part of the recovery process. When the database is booted
      * in replication slave mode, however, recovery will not get to
@@ -5113,7 +5119,7 @@
         throws StandardException{
 
         if (SanityManager.DEBUG) {
-            SanityManager.ASSERT(!inReplicationSlaveMode, 
+            SanityManager.ASSERT(inReplicationSlaveMode, 
                                  "This method should only be used when"
                                  + " in slave replication mode");
         }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/modules.properties
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/modules.properties?rev=611759&r1=611758&r2=611759&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/modules.properties (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/modules.properties Mon Jan 14 02:54:33 2008
@@ -220,6 +220,9 @@
 derby.module.database=org.apache.derby.impl.db.BasicDatabase
 cloudscape.config.database=all
 
+derby.module.database.slave=org.apache.derby.impl.db.SlaveDatabase
+cloudscape.config.database.slave=all
+
 derby.module.lf=org.apache.derby.impl.sql.GenericLanguageFactory
 cloudscape.config.lf=all