/* Copyright 2010-2015 MongoDB 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.Generic;
using System.Threading;
namespace MongoDB.Bson.IO
{
///
/// Represents a pool of chunks.
///
public sealed class BsonChunkPool : IBsonChunkSource
{
#region static
// static fields
private static BsonChunkPool __default = new BsonChunkPool(8192, 64 * 1024); // 512MiB of 64KiB chunks
// static properties
///
/// Gets or sets the default chunk pool.
///
///
/// The default chunk pool.
///
public static BsonChunkPool Default
{
get { return __default; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
__default = value;
}
}
#endregion
// private fields
private Stack _chunks = new Stack();
private readonly int _chunkSize;
private bool _disposed;
private readonly object _lock = new object();
private readonly int _maxChunkCount;
// constructors
///
/// Initializes a new instance of the class.
///
/// The maximum number of chunks to keep in the pool.
/// The size of each chunk.
public BsonChunkPool(int maxChunkCount, int chunkSize)
{
if (maxChunkCount < 0)
{
throw new ArgumentOutOfRangeException("maxChunkCount");
}
if (chunkSize <= 0)
{
throw new ArgumentOutOfRangeException("chunkSize");
}
_maxChunkCount = maxChunkCount;
_chunkSize = chunkSize;
}
// public properties
///
/// Gets the chunk size.
///
///
/// The chunk size.
///
public int ChunkSize
{
get { return _chunkSize; }
}
///
/// Gets the maximum size of the pool.
///
///
/// The maximum size of the pool.
///
public int MaxChunkCount
{
get { return _maxChunkCount; }
}
///
/// Gets the size of the pool.
///
///
/// The size of the pool.
///
public int ChunkCount
{
get
{
lock (_lock)
{
return _chunks.Count;
}
}
}
// public methods
///
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_chunks = null;
}
}
///
public IBsonChunk GetChunk(int requestedSize)
{
ThrowIfDisposed();
ReferenceCountedChunk referenceCountedChunk = null;
lock (_lock)
{
if (_chunks.Count > 0)
{
referenceCountedChunk = _chunks.Pop();
}
}
if (referenceCountedChunk == null)
{
var chunk = new byte[_chunkSize];
referenceCountedChunk = new ReferenceCountedChunk(chunk, this);
}
return new DisposableChunk(referenceCountedChunk);
}
// private methods
private void ReleaseChunk(ReferenceCountedChunk chunk)
{
if (!_disposed)
{
lock (_lock)
{
if (_chunks.Count < _maxChunkCount)
{
_chunks.Push(chunk);
}
// otherwise just let it get garbage collected
}
}
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
// nested types
private sealed class DisposableChunk : IBsonChunk
{
// fields
private bool _disposed;
private readonly ReferenceCountedChunk _referenceCountedChunk;
// constructors
public DisposableChunk(ReferenceCountedChunk referenceCountedChunk)
{
_referenceCountedChunk = referenceCountedChunk;
_referenceCountedChunk.IncrementReferenceCount();
}
// properties
public ArraySegment Bytes
{
get
{
ThrowIfDisposed();
return new ArraySegment(_referenceCountedChunk.Chunk);
}
}
// methods
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_referenceCountedChunk.DecrementReferenceCount();
}
}
public IBsonChunk Fork()
{
ThrowIfDisposed();
return new DisposableChunk(_referenceCountedChunk);
}
// private methods
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
}
}
private sealed class ReferenceCountedChunk
{
private byte[] _chunk;
private BsonChunkPool _pool;
private int _referenceCount;
public ReferenceCountedChunk(byte[] chunk, BsonChunkPool pool)
{
_chunk = chunk;
_pool = pool;
}
public byte[] Chunk
{
get { return _chunk; }
}
public void DecrementReferenceCount()
{
if (Interlocked.Decrement(ref _referenceCount) == 0)
{
_pool.ReleaseChunk(this);
}
}
public void IncrementReferenceCount()
{
Interlocked.Increment(ref _referenceCount);
}
}
}
}