Skip to content

Commit

Permalink
Issue: #29
Browse files Browse the repository at this point in the history
Added IrcMac, which is an abstraction layer for writing/reading from the TcpClient.  This will prevent NREs from happening since we never set this to null.

Our ReceiverThread will also return for ANY Exception, instead of swallowing it and causing an NRE (or now, an ODE) from happening over and over again..
  • Loading branch information
xforever1313 committed Jan 20, 2019
1 parent 533303a commit 2808221
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 68 deletions.
106 changes: 38 additions & 68 deletions Chaskis/ChaskisCore/IrcConnection.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright Seth Hendrick 2016-2018.
// Copyright Seth Hendrick 2016-2019.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
using SethCS.Basic;
Expand Down Expand Up @@ -58,28 +57,13 @@ public class IrcConnection : IDisposable, IConnection, IChaskisEventScheduler, I
/// </summary>
public static readonly int MaximumLength = 400;

/// <summary>
/// Connection to the server.
/// </summary>
private TcpClient connection;

private SslStream sslStream;

/// <summary>
/// Used to send commands.
/// </summary>
private StreamWriter ircWriter;
private readonly IIrcMac connection;

/// <summary>
/// Lock for the IRC writer.
/// </summary>
private readonly object ircWriterLock;

/// <summary>
/// Used to read commands.
/// </summary>
private StreamReader ircReader;

/// <summary>
/// The thread that reads.
/// </summary>
Expand Down Expand Up @@ -122,16 +106,20 @@ public class IrcConnection : IDisposable, IConnection, IChaskisEventScheduler, I
/// Constructor
/// </summary>
/// <param name="config">The configuration to use.</param>
public IrcConnection( IIrcConfig config, INonDisposableStringParsingQueue parsingQueue )
public IrcConnection( IIrcConfig config, INonDisposableStringParsingQueue parsingQueue, IIrcMac macLayer = null )
{
this.inited = false;
this.Config = new ReadOnlyIrcConfig( config );
this.IsConnected = false;

this.connection = null;
this.sslStream = null;
this.ircWriter = null;
this.ircReader = null;
if( macLayer == null )
{
this.connection = new IrcMac( config, StaticLogger.Log );
}
else
{
this.connection = macLayer;
}

this.keepReadingObject = new object();
this.KeepReading = false;
Expand Down Expand Up @@ -227,39 +215,30 @@ public void Connect()
);
}

// Connect.
this.connection = new TcpClient( this.Config.Server, this.Config.Port );

Stream stream;
if( this.Config.UseSsl )
if( this.readerThread != null )
{
StaticLogger.Log.WriteLine( "Using SSL connection." );
this.sslStream = new SslStream( this.connection.GetStream() );
this.sslStream.AuthenticateAsClient( this.Config.Server );
stream = sslStream;
}
else
{
StaticLogger.Log.WriteLine( "WARNING! Using plain text connection." );
stream = this.connection.GetStream();
throw new InvalidOperationException(
"Somehow our reader thread is not null, this should never happen. Something went wrong."
);
}

this.ircWriter = new StreamWriter( stream );
this.ircReader = new StreamReader( stream );
// Connect.
this.connection.Connect();

// Start Reading.
this.KeepReading = true;
this.readerThread = new Thread( ReaderThread );
this.readerThread.Name = this.Config.Server + " IRC reader thread";
this.readerThread = new Thread( ReaderThread )
{
Name = this.Config.Server + " IRC reader thread"
};
this.readerThread.Start();

// Per RFC-2812, the server password sets a "connection password".
// This MUST be sent before any attempt to set the username and nick name.
// Therefore, this is the first command that gets sent.
if( string.IsNullOrEmpty( this.Config.ServerPassword ) == false )
{
this.ircWriter.WriteLine( "PASS {0}", this.Config.ServerPassword );
this.ircWriter.Flush();
this.connection.WriteLine( "PASS {0}", this.Config.ServerPassword );
Thread.Sleep( this.Config.RateLimit );
}
else
Expand All @@ -271,21 +250,18 @@ public void Connect()
// This command is used at the beginning of a connection to specify the username,
// real name and initial user modes of the connecting client.
// <realname> may contain spaces, and thus must be prefixed with a colon.
this.ircWriter.WriteLine( "USER {0} 0 * :{1}", this.Config.UserName, this.Config.RealName );
this.ircWriter.Flush();
this.connection.WriteLine( "USER {0} 0 * :{1}", this.Config.UserName, this.Config.RealName );
Thread.Sleep( this.Config.RateLimit );

// NICK <nickname>
this.ircWriter.WriteLine( "NICK {0}", this.Config.Nick );
this.ircWriter.Flush();
this.connection.WriteLine( "NICK {0}", this.Config.Nick );
Thread.Sleep( this.Config.RateLimit );

// If the server has a NickServ service, tell nickserv our password
// so it registers our bot and does not change its nickname on us.
if( string.IsNullOrEmpty( this.Config.NickServPassword ) == false )
{
this.ircWriter.WriteLine( "PRIVMSG NickServ :IDENTIFY {0}", this.Config.NickServPassword );
this.ircWriter.Flush();
this.connection.WriteLine( "PRIVMSG NickServ :IDENTIFY {0}", this.Config.NickServPassword );
Thread.Sleep( this.Config.RateLimit );
}
else
Expand All @@ -304,8 +280,7 @@ public void Connect()
// If channel does not exist it will be created.
foreach( string channel in this.Config.Channels )
{
this.ircWriter.WriteLine( "JOIN {0}", channel );
this.ircWriter.Flush();
this.connection.WriteLine( "JOIN {0}", channel );

this.AddCoreEvent( ChaskisCoreEvents.JoinChannel + " " + channel );
Thread.Sleep( this.Config.RateLimit );
Expand Down Expand Up @@ -382,14 +357,13 @@ private void SendMessageHelper( string line, string channel )
// CanWrite can return true, this thread can be preempted,
// and a thread that disconnects the connection runs. Now, when this thread runs again, we try to write
// to a socket that is closed which is a problem.
if( ( this.connection == null ) || ( this.connection.Connected == false ) )
if( ( this.connection == null ) || ( this.connection.IsConnected == false ) )
{
return;
}

// PRIVMSG < msgtarget > < message >
this.ircWriter.WriteLine( "PRIVMSG {0} :{1}", channel, line );
this.ircWriter.Flush();
this.connection.WriteLine( "PRIVMSG {0} :{1}", channel, line );
}
}
);
Expand Down Expand Up @@ -482,13 +456,12 @@ public void SendRawCmd( string cmd )
{
lock( this.ircWriterLock )
{
if( ( this.connection == null ) || ( this.connection.Connected == false ) )
if( ( this.connection == null ) || ( this.connection.IsConnected == false ) )
{
return;
}

this.ircWriter.WriteLine( cmd );
this.ircWriter.Flush();
this.connection.WriteLine( cmd );
}
}
);
Expand Down Expand Up @@ -554,7 +527,7 @@ public void Disconnect()
// the writer is writing.
lock( this.ircWriterLock )
{
this.ircReader.Close();
this.connection.Disconnect();
}

// - Wait for the reader thread to exit.
Expand All @@ -568,6 +541,7 @@ public void Disconnect()
}

this.reconnector.Dispose();
this.connection.Dispose();

StaticLogger.Log.WriteLine( "Disconnect Complete." );
}
Expand All @@ -580,13 +554,7 @@ private void DisconnectHelper()
{
this.AddCoreEvent( ChaskisCoreEvents.DisconnectInProgress );

this.connection.Close();

// Reset everything to null.
this.ircWriter = null;
this.ircReader = null;
this.sslStream = null;
this.connection = null;
this.connection.Disconnect();

// We are not connected.
this.IsConnected = false;
Expand Down Expand Up @@ -706,7 +674,7 @@ private void ReaderThread()
try
{
// ReadLine blocks until we call Close() on the underlying stream.
string s = this.ircReader.ReadLine();
string s = this.connection.ReadLine();
if( ( string.IsNullOrWhiteSpace( s ) == false ) && ( string.IsNullOrEmpty( s ) == false ) )
{
// If KeepReading is set to false, we want this thread to exit.
Expand Down Expand Up @@ -754,10 +722,12 @@ private void ReaderThread()
catch( Exception err )
{
// Unexpected exception occurred. The connection probably dropped.
// Nothing we can do now except to attempt to try again.
// Nothing we can do now except to wait for the watch dog to trigger a reconnect..
StaticLogger.Log.ErrorWriteLine(
"IRC Reader Thread caught unexpected exception:" + Environment.NewLine + err + Environment.NewLine + "Wait for watchdog to reconnect..."
);

return;
}
} // End While
}
Expand Down Expand Up @@ -786,7 +756,7 @@ private void AttemptReconnect()
// We don't want any events to run and try to write to a closed connection.
lock( this.ircWriterLock )
{
this.ircReader.Close(); // Close the IRC Stream. This also closes the writer as well.
this.connection.Disconnect();
DisconnectHelper();
}
// Bad news is when we release the lock, any event that wants to write to the IRC Channel
Expand Down
Loading

0 comments on commit 2808221

Please sign in to comment.