From 23346cd0f440eb70be46c161065c7ddb55f45099 Mon Sep 17 00:00:00 2001 From: rstam Date: Mon, 18 Jul 2011 22:47:27 -0400 Subject: [PATCH] Fixed CSHARP-269. MongoGridFS.EnsureIndexes now checks actual server instance (if a RequestStart is in scope) to see if it is a primary. MongoGridFS.Download now actually uses slaveOk from database.Settings. Added overload of RequestStart with slaveOk parameter. Added RequestConnection property to MongoServer that can be used to get the actual connection reserved by RequestStart. Made get accessors of MongoConnection properties public so client code can get information about a connection. --- Bson/IO/JsonWriter.cs | 2 +- Driver/Core/MongoDatabase.cs | 15 +++++- Driver/Core/MongoServer.cs | 52 +++++++++++++++++-- Driver/GridFS/MongoGridFS.cs | 18 +++++-- Driver/GridFS/MongoGridFSFileInfo.cs | 2 +- Driver/Internal/MongoConnection.cs | 56 ++++++++++++++++---- DriverOnlineTests/DriverOnlineTests.csproj | 1 + DriverOnlineTests/Jira/CSharp269Tests.cs | 60 ++++++++++++++++++++++ 8 files changed, 186 insertions(+), 20 deletions(-) create mode 100644 DriverOnlineTests/Jira/CSharp269Tests.cs diff --git a/Bson/IO/JsonWriter.cs b/Bson/IO/JsonWriter.cs index 74e0787271..aab0e1c152 100644 --- a/Bson/IO/JsonWriter.cs +++ b/Bson/IO/JsonWriter.cs @@ -104,7 +104,7 @@ namespace MongoDB.Bson.IO { throw new ArgumentException("GuidRepresentation for binary subtype UuidLegacy must not be Standard."); } if (subType == BsonBinarySubType.UuidStandard && guidRepresentation != GuidRepresentation.Standard) { - var message = string.Format("GuidRepresentation for binary subtype UuidStandard must Standard, not {0}.", guidRepresentation); + var message = string.Format("GuidRepresentation for binary subtype UuidStandard must be Standard, not {0}.", guidRepresentation); throw new ArgumentException(message); } if (settings.ShellVersion >= new Version(2, 0, 0)) { diff --git a/Driver/Core/MongoDatabase.cs b/Driver/Core/MongoDatabase.cs index ace93e6189..87608f515b 100644 --- a/Driver/Core/MongoDatabase.cs +++ b/Driver/Core/MongoDatabase.cs @@ -754,7 +754,20 @@ namespace MongoDB.Driver { /// /// A helper object that implements IDisposable and calls from the Dispose method. public virtual IDisposable RequestStart() { - return server.RequestStart(this); + return RequestStart(false); // not slaveOk + } + + /// + /// Lets the server know that this thread is about to begin a series of related operations that must all occur + /// on the same connection. The return value of this method implements IDisposable and can be placed in a + /// using statement (in which case RequestDone will be called automatically when leaving the using statement). + /// + /// Whether queries should be sent to secondary servers. + /// A helper object that implements IDisposable and calls from the Dispose method. + public virtual IDisposable RequestStart( + bool slaveOk + ) { + return server.RequestStart(this, slaveOk); } // TODO: mongo shell has ResetError at the database level diff --git a/Driver/Core/MongoServer.cs b/Driver/Core/MongoServer.cs index b1e40ea1bc..a5ba13c057 100644 --- a/Driver/Core/MongoServer.cs +++ b/Driver/Core/MongoServer.cs @@ -293,6 +293,23 @@ namespace MongoDB.Driver { internal set { replicaSetName = value; } } + /// + /// Gets the connection reserved by the current RequestStart scope (null if not in the scope of a RequestStart). + /// + public virtual MongoConnection RequestConnection { + get { + int threadId = Thread.CurrentThread.ManagedThreadId; + lock (requestsLock) { + Request request; + if (requests.TryGetValue(threadId, out request)) { + return request.Connection; + } else { + return null; + } + } + } + } + /// /// Gets the RequestStart nesting level for the current thread. /// @@ -757,21 +774,39 @@ namespace MongoDB.Driver { /// A helper object that implements IDisposable and calls from the Dispose method. public virtual IDisposable RequestStart( MongoDatabase initialDatabase + ) { + return RequestStart(initialDatabase, false); // not slaveOk + } + + /// + /// Lets the server know that this thread is about to begin a series of related operations that must all occur + /// on the same connection. The return value of this method implements IDisposable and can be placed in a + /// using statement (in which case RequestDone will be called automatically when leaving the using statement). + /// + /// One of the databases involved in the related operations. + /// Whether queries should be sent to secondary servers. + /// A helper object that implements IDisposable and calls from the Dispose method. + public virtual IDisposable RequestStart( + MongoDatabase initialDatabase, + bool slaveOk ) { int threadId = Thread.CurrentThread.ManagedThreadId; lock (requestsLock) { Request request; if (requests.TryGetValue(threadId, out request)) { + if (!slaveOk && request.SlaveOk) { + throw new InvalidOperationException("A nested call to RequestStart with slaveOk false is not allowed when the original call to RequestStart was made with slaveOk true."); + } request.NestingLevel++; return new RequestStartResult(this); } } // get the connection outside of the lock - var connection = AcquireConnection(initialDatabase, false); // not slaveOk + var connection = AcquireConnection(initialDatabase, slaveOk); lock (requestsLock) { - var request = new Request(connection); + var request = new Request(connection, slaveOk); requests.Add(threadId, request); return new RequestStartResult(this); } @@ -882,6 +917,9 @@ namespace MongoDB.Driver { lock (requestsLock) { Request request; if (requests.TryGetValue(threadId, out request)) { + if (!slaveOk && request.SlaveOk) { + throw new InvalidOperationException("A call to AcquireConnection with slaveOk false is not allowed when the current RequestStart was made with slaveOk true."); + } request.Connection.CheckAuthentication(database); // will throw exception if authentication fails return request.Connection; } @@ -1041,14 +1079,17 @@ namespace MongoDB.Driver { private class Request { #region private fields private MongoConnection connection; + private bool slaveOk; private int nestingLevel; #endregion #region constructors public Request( - MongoConnection connection + MongoConnection connection, + bool slaveOk ) { this.connection = connection; + this.slaveOk = slaveOk; this.nestingLevel = 1; } #endregion @@ -1063,6 +1104,11 @@ namespace MongoDB.Driver { get { return nestingLevel; } set { nestingLevel = value; } } + + public bool SlaveOk { + get { return slaveOk; } + internal set { slaveOk = value; } + } #endregion } diff --git a/Driver/GridFS/MongoGridFS.cs b/Driver/GridFS/MongoGridFS.cs index 6ffb460e85..1ca752821d 100644 --- a/Driver/GridFS/MongoGridFS.cs +++ b/Driver/GridFS/MongoGridFS.cs @@ -269,7 +269,7 @@ namespace MongoDB.Driver.GridFS { Stream stream, MongoGridFSFileInfo fileInfo ) { - using (database.RequestStart()) { + using (database.RequestStart(database.Settings.SlaveOk)) { EnsureIndexes(); string md5Client; @@ -441,8 +441,18 @@ namespace MongoDB.Driver.GridFS { int maxFiles ) { // don't try to create indexes on secondaries - if (files.Settings.SlaveOk) { - return; + var requestConnection = database.Server.RequestConnection; + if (requestConnection != null) { + // check whether the actual server instance we are using is a primary + var serverInstance = requestConnection.ServerInstance; + if (!serverInstance.IsPrimary) { + return; + } + } else { + // check whether we are guaranteed to use a primary + if (database.Settings.SlaveOk) { + return; + } } // avoid round trip to count files if possible @@ -796,7 +806,7 @@ namespace MongoDB.Driver.GridFS { string remoteFileName, MongoGridFSCreateOptions createOptions ) { - using (database.RequestStart()) { + using (database.RequestStart(false)) { // not slaveOk EnsureIndexes(); var files_id = createOptions.Id ?? BsonObjectId.GenerateNewId(); diff --git a/Driver/GridFS/MongoGridFSFileInfo.cs b/Driver/GridFS/MongoGridFSFileInfo.cs index f6f578bf65..0d4019663e 100644 --- a/Driver/GridFS/MongoGridFSFileInfo.cs +++ b/Driver/GridFS/MongoGridFSFileInfo.cs @@ -315,7 +315,7 @@ namespace MongoDB.Driver.GridFS { /// public void Delete() { if (Exists) { - using (gridFS.Database.RequestStart()) { + using (gridFS.Database.RequestStart(false)) { // not slaveOk gridFS.EnsureIndexes(); gridFS.Files.Remove(Query.EQ("_id", id), gridFS.Settings.SafeMode); gridFS.Chunks.Remove(Query.EQ("files_id", id), gridFS.Settings.SafeMode); diff --git a/Driver/Internal/MongoConnection.cs b/Driver/Internal/MongoConnection.cs index ca6955e060..6ed1671e9f 100644 --- a/Driver/Internal/MongoConnection.cs +++ b/Driver/Internal/MongoConnection.cs @@ -26,14 +26,32 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Internal { - internal enum MongoConnectionState { + /// + /// Represents the state of a connection. + /// + public enum MongoConnectionState { + /// + /// The connection has not yet been initialized. + /// Initial, + /// + /// The connection is open. + /// Open, + /// + /// The connection is damaged. + /// Damaged, + /// + /// The connection is closed. + /// Closed } - internal class MongoConnection { + /// + /// Represents a connection to a MongoServerInstance. + /// + public class MongoConnection { #region private fields private object connectionLock = new object(); private MongoServerInstance serverInstance; @@ -57,29 +75,47 @@ namespace MongoDB.Driver.Internal { } #endregion - #region internal properties - internal MongoConnectionPool ConnectionPool { + #region public properties + /// + /// Gets the connection pool that this connection belongs to. + /// + public MongoConnectionPool ConnectionPool { get { return connectionPool; } } - internal DateTime CreatedAt { + /// + /// Gets the DateTime that this connection was created at. + /// + public DateTime CreatedAt { get { return createdAt; } } - internal DateTime LastUsedAt { + /// + /// Gets the DateTime that this connection was last used at. + /// + public DateTime LastUsedAt { get { return lastUsedAt; } - set { lastUsedAt = value; } + internal set { lastUsedAt = value; } } - internal int MessageCounter { + /// + /// Gets a count of the number of messages that have been sent using this connection. + /// + public int MessageCounter { get { return messageCounter; } } - internal MongoServerInstance ServerInstance { + /// + /// Gets the server instance this connection is connected to. + /// + public MongoServerInstance ServerInstance { get { return serverInstance; } } - internal MongoConnectionState State { + /// + /// Gets the state of this connection. + /// + public MongoConnectionState State { get { return state; } } #endregion diff --git a/DriverOnlineTests/DriverOnlineTests.csproj b/DriverOnlineTests/DriverOnlineTests.csproj index 33f87c7569..20a5e8c4e2 100644 --- a/DriverOnlineTests/DriverOnlineTests.csproj +++ b/DriverOnlineTests/DriverOnlineTests.csproj @@ -106,6 +106,7 @@ + diff --git a/DriverOnlineTests/Jira/CSharp269Tests.cs b/DriverOnlineTests/Jira/CSharp269Tests.cs new file mode 100644 index 0000000000..b71fb6bffc --- /dev/null +++ b/DriverOnlineTests/Jira/CSharp269Tests.cs @@ -0,0 +1,60 @@ +/* Copyright 2010-2011 10gen 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. +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using NUnit.Framework; + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using MongoDB.Driver.Builders; + +namespace MongoDB.DriverOnlineTests.Jira.CSharp269 { + [TestFixture] + public class CSharp269Tests { + private MongoServer server; + private MongoDatabase database; + + [TestFixtureSetUp] + public void TestFixtureSetup() { + server = MongoServer.Create("mongodb://localhost/?safe=true;slaveOk=true"); + database = server["onlinetests"]; + database.GridFS.Files.Drop(); + database.GridFS.Chunks.Drop(); + } + + [Test] + public void TestUploadAndDownload() { + var text = "HelloWorld"; + var bytes = Encoding.UTF8.GetBytes(text); + using (var stream = new MemoryStream(bytes)) { + database.GridFS.Upload(stream, "HelloWorld.txt"); + } + + using (var stream = new MemoryStream()) { + database.GridFS.Download(stream, "HelloWorld.txt"); + var downloadedBytes = stream.ToArray(); + var downloadedText = Encoding.UTF8.GetString(downloadedBytes); + Assert.AreEqual("HelloWorld", downloadedText); + } + } + } +}