blob: 7a3474ec4d5789b2576f17fc232cb7ecc8929a6a [file] [log] [blame]
// <copyright file="SocketLock.cs" company="WebDriver Committers">
// Copyright 2007-2011 WebDriver committers
// Copyright 2007-2011 Google Inc.
// Portions copyright 2011 Software Freedom Conservancy
//
// 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.
// </copyright>
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace OpenQA.Selenium.Firefox.Internal
{
/// <summary>
/// Provides a mutex-like lock on a socket.
/// </summary>
internal class SocketLock : ILock
{
#region Private members
private static int delayBetweenSocketChecks = 100;
private int lockPort;
private Socket lockSocket;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="SocketLock"/> class.
/// </summary>
/// <param name="lockPort">Port to use to acquire the lock.</param>
/// <remarks>The <see cref="SocketLock"/> class will attempt to acquire the
/// specified port number, and wait for it to become free.</remarks>
public SocketLock(int lockPort)
{
this.lockPort = lockPort;
this.lockSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.PreventSocketInheritance();
}
#endregion
#region ILock Members
/// <summary>
/// Locks the mutex port.
/// </summary>
/// <param name="timeoutInMilliseconds">The amount of time (in milliseconds) to wait for
/// the mutex port to become available.</param>
public void LockObject(long timeoutInMilliseconds)
{
IPHostEntry hostEntry = Dns.GetHostEntry("localhost");
// Use the first IPv4 address that we find
IPAddress endPointAddress = IPAddress.Parse("127.0.0.1");
foreach (IPAddress ip in hostEntry.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
endPointAddress = ip;
break;
}
}
IPEndPoint address = new IPEndPoint(endPointAddress, this.lockPort);
// Calculate the 'exit time' for our wait loop.
DateTime maxWait = DateTime.Now.AddMilliseconds(timeoutInMilliseconds);
// Attempt to acquire the lock until something goes wrong or we run out of time.
do
{
try
{
if (this.IsLockFree(address))
{
return;
}
Thread.Sleep(delayBetweenSocketChecks);
}
catch (ThreadInterruptedException e)
{
throw new WebDriverException("the thread was interrupted", e);
}
catch (IOException e)
{
throw new WebDriverException("An unexpected error occurred", e);
}
}
while (DateTime.Now < maxWait);
throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, "Unable to bind to locking port {0} within {1} ms", this.lockPort, timeoutInMilliseconds));
}
/// <summary>
/// Unlocks the mutex port.
/// </summary>
public void UnlockObject()
{
try
{
this.lockSocket.Close();
}
catch (IOException e)
{
throw new WebDriverException("An error occured unlocking the object", e);
}
}
#endregion
#region IDisposable Members
/// <summary>
/// Releases all resources associated with this <see cref="SocketLock"/>
/// </summary>
public void Dispose()
{
if (this.lockSocket != null && this.lockSocket.Connected)
{
this.lockSocket.Close();
}
GC.SuppressFinalize(this);
}
#endregion
#region Support methods
private bool IsLockFree(IPEndPoint address)
{
try
{
this.lockSocket.Bind(address);
return true;
}
catch (SocketException)
{
return false;
}
}
private void PreventSocketInheritance()
{
// TODO (JimEvans): Handle the non-Windows case.
if (Platform.CurrentPlatform.IsPlatformType(PlatformType.Windows))
{
NativeMethods.SetHandleInformation(this.lockSocket.Handle, NativeMethods.HandleInformation.Inherit | NativeMethods.HandleInformation.ProtectFromClose, NativeMethods.HandleInformation.None);
}
}
#endregion
}
}