blob: ca1114d8c2e72e4f9d0deecb4111540889bcd0e4 [file] [log] [blame]
using J2N.Threading;
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Lucene.Net.Store
{
/*
* 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.
*/
using IOUtils = Lucene.Net.Util.IOUtils;
/// <summary>
/// Simple standalone server that must be running when you
/// use <see cref="VerifyingLockFactory"/>. This server simply
/// verifies at most one process holds the lock at a time.
/// <para />
/// LUCENENET specific: In the Java implementation, this class' Main method
/// was intended to be called from the command line. However, in .NET a
/// method within a DLL can't be directly called from the command line so we
/// provide a <see href="https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools">.NET tool</see>,
/// <see href="https://www.nuget.org/packages/lucene-cli">lucene-cli</see>,
/// with a command that maps to that method:
/// lock verify-server
/// </summary>
/// <seealso cref="VerifyingLockFactory"/>
/// <seealso cref="LockStressTest"/>
public static class LockVerifyServer // LUCENENET specific: CA1052 Static holder types should be Static or NotInheritable
{
/// <summary>
/// LUCENENET specific: In the Java implementation, this Main method
/// was intended to be called from the command line. However, in .NET a
/// method within a DLL can't be directly called from the command line so we
/// provide a <see href="https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools">.NET tool</see>,
/// <see href="https://www.nuget.org/packages/lucene-cli">lucene-cli</see>,
/// with a command that maps to this method:
/// lock verify-server
/// </summary>
/// <param name="args">The command line arguments</param>
/// <exception cref="ArgumentException">Thrown if the incorrect number of arguments are provided</exception>
[STAThread]
public static void Main(string[] args)
{
if (args.Length != 2)
{
// LUCENENET specific - our wrapper console shows the correct usage
throw new ArgumentException("LockVerifyServer requires 2 arguments", nameof(args));
//Console.WriteLine("Usage: java Lucene.Net.Store.LockVerifyServer bindToIp clients\n");
//Environment.FailFast("1");
}
int arg = 0;
string hostname = args[arg++];
int maxClients = Convert.ToInt32(args[arg++], CultureInfo.InvariantCulture);
IPAddress ipAddress = IPAddress.Parse(hostname);
using Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 30000);// SoTimeout = 30000; // initially 30 secs to give clients enough time to startup
s.Bind(new IPEndPoint(ipAddress, 0));
s.Listen(maxClients);
Console.WriteLine("Listening on " + ((IPEndPoint)s.LocalEndPoint).Port.ToString() + "...");
// we set the port as a sysprop, so the ANT task can read it. For that to work, this server must run in-process:
Environment.SetEnvironmentVariable("lockverifyserver.port", ((IPEndPoint)s.LocalEndPoint).Port.ToString(CultureInfo.InvariantCulture));
object localLock = new object();
int[] lockedID = new int[1];
lockedID[0] = -1;
using CountdownEvent startingGun = new CountdownEvent(1); // LUCENENET specific - dispose when finished
ThreadJob[] threads = new ThreadJob[maxClients];
for (int count = 0; count < maxClients; count++)
{
Socket cs = s.Accept();
threads[count] = new ThreadAnonymousClass(localLock, lockedID, startingGun, cs);
threads[count].Start();
}
// start
Console.WriteLine("All clients started, fire gun...");
startingGun.Signal();
// wait for all threads to finish
foreach (ThreadJob t in threads)
{
t.Join();
}
// cleanup sysprop
Environment.SetEnvironmentVariable("lockverifyserver.port", null);
Console.WriteLine("Server terminated.");
}
private sealed class ThreadAnonymousClass : ThreadJob
{
private readonly object localLock;
private readonly int[] lockedID;
private readonly CountdownEvent startingGun;
private readonly Socket cs;
public ThreadAnonymousClass(object localLock, int[] lockedID, CountdownEvent startingGun, Socket cs)
{
this.localLock = localLock;
this.lockedID = lockedID;
this.startingGun = startingGun;
this.cs = cs;
}
public override void Run()
{
using Stream stream = new NetworkStream(cs);
using BinaryReader intReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
using BinaryWriter intWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
try
{
int id = intReader.ReadInt32();
if (id < 0)
{
throw new IOException("Client closed connection before communication started.");
}
startingGun.Wait();
intWriter.Write(43);
stream.Flush();
while (true)
{
int command = stream.ReadByte();
if (command < 0)
{
return; // closed
}
UninterruptableMonitor.Enter(localLock);
try
{
int currentLock = lockedID[0];
if (currentLock == -2)
{
return; // another thread got error, so we exit, too!
}
switch (command)
{
case 1:
// Locked
if (currentLock != -1)
{
lockedID[0] = -2;
throw IllegalStateException.Create("id " + id + " got lock, but " + currentLock + " already holds the lock");
}
lockedID[0] = id;
break;
case 0:
// Unlocked
if (currentLock != id)
{
lockedID[0] = -2;
throw IllegalStateException.Create("id " + id + " released the lock, but " + currentLock + " is the one holding the lock");
}
lockedID[0] = -1;
break;
default:
throw RuntimeException.Create("Unrecognized command: " + command);
}
intWriter.Write((byte)command);
stream.Flush();
}
finally
{
UninterruptableMonitor.Exit(localLock);
}
}
}
catch (Exception e) when (e.IsRuntimeException() || e.IsError())
{
throw; // LUCENENET: CA2200: Rethrow to preserve stack details (https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2200-rethrow-to-preserve-stack-details)
}
catch (Exception ioe) when (ioe.IsException())
{
throw RuntimeException.Create(ioe);
}
finally
{
IOUtils.DisposeWhileHandlingException(cs);
}
}
}
}
}