Commit 9a0f258e authored by Marc Gravell's avatar Marc Gravell

RedisValue internals tweaked; writing complete; read not started

parent 4f182469
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Xunit; using Xunit;
...@@ -64,6 +65,13 @@ public void TestValues() ...@@ -64,6 +65,13 @@ public void TestValues()
private void CheckSame(RedisValue x, RedisValue y) private void CheckSame(RedisValue x, RedisValue y)
{ {
Assert.True(Equals(x, y)); Assert.True(Equals(x, y));
Assert.True(Equals(y, x));
Assert.True(EqualityComparer<RedisValue>.Default.Equals(x, y));
Assert.True(EqualityComparer<RedisValue>.Default.Equals(y, x));
Assert.True(x == y);
Assert.True(y == x);
Assert.False(x != y);
Assert.False(y != x);
Assert.True(x.Equals(y)); Assert.True(x.Equals(y));
Assert.True(y.Equals(x)); Assert.True(y.Equals(x));
Assert.True(x.GetHashCode() == y.GetHashCode()); Assert.True(x.GetHashCode() == y.GetHashCode());
...@@ -72,6 +80,13 @@ private void CheckSame(RedisValue x, RedisValue y) ...@@ -72,6 +80,13 @@ private void CheckSame(RedisValue x, RedisValue y)
private void CheckNotSame(RedisValue x, RedisValue y) private void CheckNotSame(RedisValue x, RedisValue y)
{ {
Assert.False(Equals(x, y)); Assert.False(Equals(x, y));
Assert.False(Equals(y, x));
Assert.False(EqualityComparer<RedisValue>.Default.Equals(x, y));
Assert.False(EqualityComparer<RedisValue>.Default.Equals(y, x));
Assert.False(x == y);
Assert.False(y == x);
Assert.True(x != y);
Assert.True(y != x);
Assert.False(x.Equals(y)); Assert.False(x.Equals(y));
Assert.False(y.Equals(x)); Assert.False(y.Equals(x));
Assert.False(x.GetHashCode() == y.GetHashCode()); // well, very unlikely Assert.False(x.GetHashCode() == y.GetHashCode()); // well, very unlikely
...@@ -107,9 +122,9 @@ private void CheckNull(RedisValue value) ...@@ -107,9 +122,9 @@ private void CheckNull(RedisValue value)
Assert.Equal(0L, (long)value); Assert.Equal(0L, (long)value);
CheckSame(value, value); CheckSame(value, value);
CheckSame(value, default(RedisValue)); //CheckSame(value, default(RedisValue));
CheckSame(value, (string)null); //CheckSame(value, (string)null);
CheckSame(value, (byte[])null); //CheckSame(value, (byte[])null);
} }
[Fact] [Fact]
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<!--<DefineConstants>$(DefineConstants);LOGOUTPUT</DefineConstants>-->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
......
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.IO.Pipelines;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
...@@ -324,28 +326,32 @@ partial class ConnectionMultiplexer ...@@ -324,28 +326,32 @@ partial class ConnectionMultiplexer
partial class PhysicalConnection partial class PhysicalConnection
{ {
private Stream echo; //private Stream echo;
partial void OnCreateEcho() //partial void OnCreateEcho()
//{
// if (!string.IsNullOrEmpty(ConnectionMultiplexer.EchoPath))
// {
// string fullPath = Path.Combine(ConnectionMultiplexer.EchoPath,
// Regex.Replace(physicalName, @"[\-\.\@\#\:]", "_"));
// echo = File.Open(Path.ChangeExtension(fullPath, "txt"), FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
// }
//}
//partial void OnCloseEcho()
//{
// if (echo != null)
// {
// try { echo.Close(); } catch { }
// try { echo.Dispose(); } catch { }
// echo = null;
// }
//}
partial void OnWrapForLogging(ref IDuplexPipe pipe, string name)
{ {
if (!string.IsNullOrEmpty(ConnectionMultiplexer.EchoPath)) foreach(var c in Path.GetInvalidFileNameChars())
{ {
string fullPath = Path.Combine(ConnectionMultiplexer.EchoPath, name = name.Replace(c, '_');
Regex.Replace(physicalName, @"[\-\.\@\#\:]", "_"));
echo = File.Open(Path.ChangeExtension(fullPath, "txt"), FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
} }
} pipe = new LoggingPipe(pipe, $"{name}.in", $"{name}.out");
partial void OnCloseEcho()
{
if (echo != null)
{
try { echo.Close(); } catch { }
try { echo.Dispose(); } catch { }
echo = null;
}
}
partial void OnWrapForLogging(ref Stream stream, string name)
{
stream = new LoggingTextStream(stream, physicalName, echo);
} }
} }
#endif #endif
......
namespace StackExchange.Redis using System;
using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Runtime.InteropServices;
namespace StackExchange.Redis
{ {
#if LOGOUTPUT #if LOGOUTPUT
sealed class LoggingTextStream : Stream sealed class LoggingPipe : IDuplexPipe
{ {
[Conditional("VERBOSE")] private IDuplexPipe _inner;
void Trace(string value, [CallerMemberName] string caller = null)
{
Debug.WriteLine(value, this.category + ":" + caller);
}
[Conditional("VERBOSE")]
void Trace(char value, [CallerMemberName] string caller = null)
{
Debug.WriteLine(value, this.category + ":" + caller);
}
private readonly Stream stream, echo; public LoggingPipe(IDuplexPipe inner, string inPath, string outPath)
private readonly string category;
public LoggingTextStream(Stream stream, string category, Stream echo)
{ {
if (stream == null) throw new ArgumentNullException("stream"); _inner = inner;
if (string.IsNullOrWhiteSpace(category)) category = GetType().Name; if (string.IsNullOrWhiteSpace(inPath))
this.stream = stream;
this.category = category;
this.echo = echo;
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
asyncBuffer = buffer;
asyncOffset = offset;
asyncCount = count;
return stream.BeginRead(buffer, offset, count, callback, state);
}
private volatile byte[] asyncBuffer;
private volatile int asyncOffset, asyncCount;
public override int EndRead(IAsyncResult asyncResult)
{
int bytes = stream.EndRead(asyncResult);
if (bytes <= 0)
{ {
Trace("<EOF>"); Input = inner.Input;
} }
else else
{ {
Trace(Encoding.UTF8.GetString(asyncBuffer, asyncOffset, asyncCount)); var pipe = new Pipe();
} Input = pipe.Reader;
return bytes; CloneAsync(inPath, inner.Input, pipe.Writer);
}
public override bool CanRead { get { return stream.CanRead; } }
public override bool CanSeek { get { return stream.CanSeek; } }
public override bool CanWrite { get { return stream.CanWrite; } }
public override bool CanTimeout { get { return stream.CanTimeout; } }
public override long Length { get { return stream.Length; } }
public override long Position
{
get { return stream.Position; }
set { stream.Position = value; }
}
public override int ReadTimeout
{
get { return stream.ReadTimeout; }
set { stream.ReadTimeout = value; }
}
public override int WriteTimeout
{
get { return stream.WriteTimeout; }
set { stream.WriteTimeout = value; }
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
stream.Dispose();
if (echo != null) echo.Flush();
}
base.Dispose(disposing);
}
public override void Close()
{
Trace("Close");
stream.Close();
if (echo != null) echo.Close();
base.Close();
}
public override void Flush()
{
Trace("Flush");
stream.Flush();
if (echo != null) echo.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return stream.Seek(offset, origin);
}
public override void SetLength(long value)
{
stream.SetLength(value);
}
public override void WriteByte(byte value)
{
Trace((char)value);
stream.WriteByte(value);
if (echo != null) echo.WriteByte(value);
}
public override int ReadByte()
{
int value = stream.ReadByte();
if(value < 0)
{
Trace("<EOF>");
} else
{
Trace((char)value);
} }
return value;
} if (string.IsNullOrWhiteSpace(outPath))
public override int Read(byte[] buffer, int offset, int count)
{
int bytes = stream.Read(buffer, offset, count);
if(bytes <= 0)
{ {
Trace("<EOF>"); Output = inner.Output;
} }
else else
{ {
Trace(Encoding.UTF8.GetString(buffer, offset, bytes)); var pipe = new Pipe();
Output = pipe.Writer;
CloneAsync(outPath, pipe.Reader, inner.Output);
} }
return bytes;
} }
public override void Write(byte[] buffer, int offset, int count)
private async void CloneAsync(string path, PipeReader from, PipeWriter to)
{ {
if (count != 0) to.OnReaderCompleted((ex, o) => ((PipeReader)o).Complete(ex), from);
from.OnWriterCompleted((ex, o) => ((PipeWriter)o).Complete(ex), to);
while(true)
{ {
Trace(Encoding.UTF8.GetString(buffer, offset, count)); var result = await from.ReadAsync();
var buffer = result.Buffer;
if (result.IsCompleted && buffer.IsEmpty) break;
using (var file = new FileStream(path, FileMode.Append, FileAccess.Write))
{
foreach (var segment in buffer)
{
// append it to the file
bool leased = false;
if (!MemoryMarshal.TryGetArray(segment, out var arr))
{
var tmp = ArrayPool<byte>.Shared.Rent(segment.Length);
segment.CopyTo(tmp);
arr = new ArraySegment<byte>(tmp, 0, segment.Length);
leased = true;
}
await file.WriteAsync(arr.Array, arr.Offset, arr.Count);
await file.FlushAsync();
if (leased) ArrayPool<byte>.Shared.Return(arr.Array);
// and flush it upstream
await to.WriteAsync(segment);
}
}
from.AdvanceTo(buffer.End);
} }
stream.Write(buffer, offset, count);
if (echo != null) echo.Write(buffer, offset, count);
} }
public PipeReader Input { get; }
public PipeWriter Output { get; }
} }
#endif #endif
} }
...@@ -421,13 +421,24 @@ internal void Write(RedisChannel channel) ...@@ -421,13 +421,24 @@ internal void Write(RedisChannel channel)
internal void Write(RedisValue value) internal void Write(RedisValue value)
{ {
if (value.IsInteger) switch(value.Type)
{ {
WriteUnified(_ioPipe.Output, (long)value); case RedisValue.StorageType.Null:
} WriteUnified(_ioPipe.Output, (byte[])null);
else break;
{ case RedisValue.StorageType.Int64:
WriteUnified(_ioPipe.Output, (byte[])value); WriteUnified(_ioPipe.Output, (long)value);
break;
case RedisValue.StorageType.Double: // use string
case RedisValue.StorageType.String:
WriteUnified(_ioPipe.Output, null, (string)value);
break;
case RedisValue.StorageType.Raw:
WriteUnified(_ioPipe.Output, ((ReadOnlyMemory<byte>)value).Span);
break;
default:
throw new InvalidOperationException($"Unexpected {value.Type} value: '{value}'");
} }
} }
...@@ -492,7 +503,7 @@ static void WriteCrlf(PipeWriter writer) ...@@ -492,7 +503,7 @@ static void WriteCrlf(PipeWriter writer)
span[1] = (byte)'\n'; span[1] = (byte)'\n';
writer.Advance(2); writer.Advance(2);
} }
private static int WriteRaw(Span<byte> span, long value, bool withLengthPrefix = false, int offset = 0) internal static int WriteRaw(Span<byte> span, long value, bool withLengthPrefix = false, int offset = 0)
{ {
if (value >= 0 && value <= 9) if (value >= 0 && value <= 9)
{ {
...@@ -594,16 +605,24 @@ internal void WakeWriterAndCheckForThrottle() ...@@ -594,16 +605,24 @@ internal void WakeWriterAndCheckForThrottle()
static readonly byte[] NullBulkString = Encoding.ASCII.GetBytes("$-1\r\n"), EmptyBulkString = Encoding.ASCII.GetBytes("$0\r\n\r\n"); static readonly byte[] NullBulkString = Encoding.ASCII.GetBytes("$-1\r\n"), EmptyBulkString = Encoding.ASCII.GetBytes("$0\r\n\r\n");
private static void WriteUnified(PipeWriter writer, byte[] value) private static void WriteUnified(PipeWriter writer, byte[] value)
{ {
const int MaxQuickSpanSize = 512;
// ${len}\r\n = 3 + MaxInt32TextLen
// {value}\r\n = 2 + value.Length
if (value == null) if (value == null)
{ {
// special case: // special case:
writer.Write(NullBulkString); writer.Write(NullBulkString);
} }
else if (value.Length == 0) else
{
WriteUnified(writer, new ReadOnlySpan<byte>(value));
}
}
private static void WriteUnified(PipeWriter writer, ReadOnlySpan<byte> value)
{
// ${len}\r\n = 3 + MaxInt32TextLen
// {value}\r\n = 2 + value.Length
const int MaxQuickSpanSize = 512;
if (value.Length == 0)
{ {
// special case: // special case:
writer.Write(EmptyBulkString); writer.Write(EmptyBulkString);
...@@ -611,7 +630,8 @@ private static void WriteUnified(PipeWriter writer, byte[] value) ...@@ -611,7 +630,8 @@ private static void WriteUnified(PipeWriter writer, byte[] value)
else if (value.Length <= MaxQuickSpanSize) else if (value.Length <= MaxQuickSpanSize)
{ {
var span = writer.GetSpan(5 + MaxInt32TextLen + value.Length); var span = writer.GetSpan(5 + MaxInt32TextLen + value.Length);
int bytes = WriteUnified(span, value); span[0] = (byte)'$';
int bytes = WriteUnified(span, value, 1);
writer.Advance(bytes); writer.Advance(bytes);
} }
else else
...@@ -619,7 +639,7 @@ private static void WriteUnified(PipeWriter writer, byte[] value) ...@@ -619,7 +639,7 @@ private static void WriteUnified(PipeWriter writer, byte[] value)
// too big to guarantee can do in a single span // too big to guarantee can do in a single span
var span = writer.GetSpan(3 + MaxInt32TextLen); var span = writer.GetSpan(3 + MaxInt32TextLen);
span[0] = (byte)'$'; span[0] = (byte)'$';
int bytes = WriteRaw(span, value.LongLength, offset: 1); int bytes = WriteRaw(span, value.Length, offset: 1);
writer.Advance(bytes); writer.Advance(bytes);
writer.Write(value); writer.Write(value);
...@@ -636,13 +656,18 @@ private static int WriteUnified(Span<byte> span, byte[] value, int offset = 0) ...@@ -636,13 +656,18 @@ private static int WriteUnified(Span<byte> span, byte[] value, int offset = 0)
} }
else else
{ {
offset = WriteRaw(span, value.Length, offset: offset); offset = WriteUnified(span, new ReadOnlySpan<byte>(value), offset);
new ReadOnlySpan<byte>(value).CopyTo(span.Slice(offset, value.Length));
offset += value.Length;
offset = WriteCrlf(span, offset);
} }
return offset; return offset;
} }
private static int WriteUnified(Span<byte> span, ReadOnlySpan<byte> value, int offset = 0)
{
offset = WriteRaw(span, value.Length, offset: offset);
value.CopyTo(span.Slice(offset, value.Length));
offset += value.Length;
offset = WriteCrlf(span, offset);
return offset;
}
internal void WriteSha1AsHex(byte[] value) internal void WriteSha1AsHex(byte[] value)
{ {
......
...@@ -2484,7 +2484,9 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -2484,7 +2484,9 @@ internal override void WriteImpl(PhysicalConnection physical)
} }
else else
{ // recognises well-known types { // recognises well-known types
physical.Write(RedisValue.Parse(arg)); var val = RedisValue.TryParse(arg);
if (val.IsNull && arg != null) throw new InvalidCastException($"Unable to parse value: '{arg}'");
physical.Write(val);
} }
} }
} }
......
...@@ -82,22 +82,22 @@ public bool IsNullOrEmpty ...@@ -82,22 +82,22 @@ public bool IsNullOrEmpty
/// <param name="y">The second <see cref="RedisValue"/> to compare.</param> /// <param name="y">The second <see cref="RedisValue"/> to compare.</param>
public static bool operator ==(RedisValue x, RedisValue y) public static bool operator ==(RedisValue x, RedisValue y)
{ {
CompareType xType = x.Type, yType = y.Type; StorageType xType = x.Type, yType = y.Type;
if (xType == CompareType.Null) return yType == CompareType.Null; if (xType == StorageType.Null) return yType == StorageType.Null;
if (xType == CompareType.Null) return false; if (xType == StorageType.Null) return false;
if (xType == yType) if (xType == yType)
{ {
switch (xType) switch (xType)
{ {
case CompareType.Double: case StorageType.Double:
return x.OverlappedValueDouble == y.OverlappedValueDouble; return x.OverlappedValueDouble == y.OverlappedValueDouble;
case CompareType.Int64: case StorageType.Int64:
return x._overlappedValue64 == y._overlappedValue64; return x._overlappedValue64 == y._overlappedValue64;
case CompareType.String: case StorageType.String:
return (string)x._objectOrSentinel == (string)y._objectOrSentinel; return (string)x._objectOrSentinel == (string)y._objectOrSentinel;
case CompareType.Raw: case StorageType.Raw:
return x._memory.Span.SequenceEqual(y._memory.Span); return x._memory.Span.SequenceEqual(y._memory.Span);
} }
} }
...@@ -113,7 +113,7 @@ public bool IsNullOrEmpty ...@@ -113,7 +113,7 @@ public bool IsNullOrEmpty
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (obj == null) return IsNull; if (obj == null) return IsNull;
if (obj is RedisValue typed) return Equals(typed);
var other = TryParse(obj); var other = TryParse(obj);
if (other.IsNull) return false; // parse fail if (other.IsNull) return false; // parse fail
return this == other; return this == other;
...@@ -132,15 +132,15 @@ public override int GetHashCode() ...@@ -132,15 +132,15 @@ public override int GetHashCode()
{ {
switch (Type) switch (Type)
{ {
case CompareType.Null: case StorageType.Null:
return -1; return -1;
case CompareType.Double: case StorageType.Double:
return OverlappedValueDouble.GetHashCode(); return OverlappedValueDouble.GetHashCode();
case CompareType.Int64: case StorageType.Int64:
return _overlappedValue64.GetHashCode(); return _overlappedValue64.GetHashCode();
case CompareType.Raw: case StorageType.Raw:
return GetHashCode(_memory); return GetHashCode(_memory);
case CompareType.String: case StorageType.String:
default: default:
return _objectOrSentinel.GetHashCode(); return _objectOrSentinel.GetHashCode();
} }
...@@ -264,22 +264,22 @@ internal void AssertNotNull() ...@@ -264,22 +264,22 @@ internal void AssertNotNull()
if (IsNull) throw new ArgumentException("A null value is not valid in this context"); if (IsNull) throw new ArgumentException("A null value is not valid in this context");
} }
private enum CompareType internal enum StorageType
{ {
Null, Int64, Double, Raw, String, Null, Int64, Double, Raw, String,
} }
private CompareType Type internal StorageType Type
{ {
get get
{ {
var objectOrSentinel = _objectOrSentinel; var objectOrSentinel = _objectOrSentinel;
if (objectOrSentinel == null) return CompareType.Null; if (objectOrSentinel == null) return StorageType.Null;
if (objectOrSentinel == Sentinel_Integer) return CompareType.Int64; if (objectOrSentinel == Sentinel_Integer) return StorageType.Int64;
if (objectOrSentinel == Sentinel_Double) return CompareType.Double; if (objectOrSentinel == Sentinel_Double) return StorageType.Double;
if (objectOrSentinel == Sentinel_Raw) return CompareType.Raw; if (objectOrSentinel == Sentinel_Raw) return StorageType.Raw;
if (objectOrSentinel is string) return CompareType.String; if (objectOrSentinel is string) return StorageType.String;
if (objectOrSentinel is byte[]) return CompareType.Raw; // doubled-up, but retaining the array if (objectOrSentinel is byte[]) return StorageType.Raw; // doubled-up, but retaining the array
throw new InvalidOperationException("Unknown type"); throw new InvalidOperationException("Unknown type");
} }
} }
...@@ -292,23 +292,23 @@ public int CompareTo(RedisValue other) ...@@ -292,23 +292,23 @@ public int CompareTo(RedisValue other)
{ {
try try
{ {
CompareType thisType = this.Type, StorageType thisType = this.Type,
otherType = other.Type; otherType = other.Type;
if (thisType == CompareType.Null) return otherType == CompareType.Null ? 0 : -1; if (thisType == StorageType.Null) return otherType == StorageType.Null ? 0 : -1;
if (otherType == CompareType.Null) return 1; if (otherType == StorageType.Null) return 1;
if (thisType == otherType) if (thisType == otherType)
{ {
switch (thisType) switch (thisType)
{ {
case CompareType.Double: case StorageType.Double:
return this.OverlappedValueDouble.CompareTo(other.OverlappedValueDouble); return this.OverlappedValueDouble.CompareTo(other.OverlappedValueDouble);
case CompareType.Int64: case StorageType.Int64:
return this._overlappedValue64.CompareTo(other._overlappedValue64); return this._overlappedValue64.CompareTo(other._overlappedValue64);
case CompareType.String: case StorageType.String:
return string.CompareOrdinal((string)this._objectOrSentinel, (string)other._objectOrSentinel); return string.CompareOrdinal((string)this._objectOrSentinel, (string)other._objectOrSentinel);
case CompareType.Raw: case StorageType.Raw:
return this._memory.Span.SequenceCompareTo(other._memory.Span); return this._memory.Span.SequenceCompareTo(other._memory.Span);
} }
} }
...@@ -479,19 +479,19 @@ internal static RedisValue TryParse(object obj) ...@@ -479,19 +479,19 @@ internal static RedisValue TryParse(object obj)
{ {
switch (value.Type) switch (value.Type)
{ {
case CompareType.Null: case StorageType.Null:
return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr")
case CompareType.Int64: case StorageType.Int64:
return value._overlappedValue64; return value._overlappedValue64;
case CompareType.Double: case StorageType.Double:
var f64 = value.OverlappedValueDouble; var f64 = value.OverlappedValueDouble;
var i64 = (long)f64; var i64 = (long)f64;
if (f64 == i64) return i64; if (f64 == i64) return i64;
break; break;
case CompareType.String: case StorageType.String:
if (TryParseInt64((string)value._objectOrSentinel, out i64)) return i64; if (TryParseInt64((string)value._objectOrSentinel, out i64)) return i64;
break; break;
case CompareType.Raw: case StorageType.Raw:
if (TryParseInt64(value._memory.Span, out i64)) return i64; if (TryParseInt64(value._memory.Span, out i64)) return i64;
break; break;
} }
...@@ -506,16 +506,16 @@ internal static RedisValue TryParse(object obj) ...@@ -506,16 +506,16 @@ internal static RedisValue TryParse(object obj)
{ {
switch (value.Type) switch (value.Type)
{ {
case CompareType.Null: case StorageType.Null:
return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr") return 0; // in redis, an arithmetic zero is kinda the same thing as not-exists (think "incr")
case CompareType.Int64: case StorageType.Int64:
return value._overlappedValue64; return value._overlappedValue64;
case CompareType.Double: case StorageType.Double:
return value.OverlappedValueDouble; return value.OverlappedValueDouble;
case CompareType.String: case StorageType.String:
if (TryParseInt64((string)value._objectOrSentinel, out var f64)) return f64; if (TryParseInt64((string)value._objectOrSentinel, out var f64)) return f64;
break; break;
case CompareType.Raw: case StorageType.Raw:
if (TryParseInt64(value._memory.Span, out f64)) return f64; if (TryParseInt64(value._memory.Span, out f64)) return f64;
break; break;
} }
...@@ -582,11 +582,11 @@ private static bool TryParseDouble(ReadOnlySpan<byte> blob, out double value) ...@@ -582,11 +582,11 @@ private static bool TryParseDouble(ReadOnlySpan<byte> blob, out double value)
{ {
switch (value.Type) switch (value.Type)
{ {
case CompareType.Null: return null; case StorageType.Null: return null;
case CompareType.Double: return Format.ToString(value.OverlappedValueDouble.ToString()); case StorageType.Double: return Format.ToString(value.OverlappedValueDouble.ToString());
case CompareType.Int64: return Format.ToString(value._overlappedValue64); case StorageType.Int64: return Format.ToString(value._overlappedValue64);
case CompareType.String: return (string)value._objectOrSentinel; case StorageType.String: return (string)value._objectOrSentinel;
case CompareType.Raw: case StorageType.Raw:
var span = value._memory.Span; var span = value._memory.Span;
if (span.IsEmpty) return ""; if (span.IsEmpty) return "";
if (span.Length == 2 && span[0] == (byte)'O' && span[1] == (byte)'K') return "OK"; // frequent special-case if (span.Length == 2 && span[0] == (byte)'O' && span[1] == (byte)'K') return "OK"; // frequent special-case
...@@ -633,10 +633,26 @@ static string ToHex(ReadOnlySpan<byte> src) ...@@ -633,10 +633,26 @@ static string ToHex(ReadOnlySpan<byte> src)
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static implicit operator byte[] (RedisValue value) public static implicit operator byte[] (RedisValue value)
{ {
if (value.Type == CompareType.Raw) switch (value.Type)
{ {
return value._objectOrSentinel is byte[] arr ? arr : value._memory.ToArray(); case StorageType.Null: return null;
case StorageType.Raw:
if (value._objectOrSentinel is byte[] arr) return arr;
if (MemoryMarshal.TryGetArray(value._memory, out var segment)
&& segment.Offset == 0
&& segment.Count == (segment.Array?.Length ?? -1))
return segment.Array; // the memory is backed by an array, and we're reading all of it
return value._memory.ToArray();
case StorageType.Int64:
Span<byte> span = stackalloc byte[PhysicalConnection.MaxInt64TextLen];
int len = PhysicalConnection.WriteRaw(span, value._overlappedValue64, false, 0);
arr = new byte[len - 2]; // don't need the CRLF
span.Slice(0, arr.Length).CopyTo(arr);
return arr;
} }
// fallback: stringify and encode
return Encoding.UTF8.GetBytes((string)value); return Encoding.UTF8.GetBytes((string)value);
} }
...@@ -644,14 +660,8 @@ static string ToHex(ReadOnlySpan<byte> src) ...@@ -644,14 +660,8 @@ static string ToHex(ReadOnlySpan<byte> src)
/// Converts a <see cref="RedisValue"/> to a ReadOnlyMemory /// Converts a <see cref="RedisValue"/> to a ReadOnlyMemory
/// </summary> /// </summary>
/// <param name="value">The <see cref="RedisValue"/> to convert.</param> /// <param name="value">The <see cref="RedisValue"/> to convert.</param>
public static implicit operator ReadOnlyMemory<byte> (RedisValue value) public static implicit operator ReadOnlyMemory<byte>(RedisValue value)
{ => value.Type == StorageType.Raw ? value._memory : (byte[])value;
if (value.Type == CompareType.Raw)
{
return value._memory;
}
return Encoding.UTF8.GetBytes((string)value);
}
TypeCode IConvertible.GetTypeCode() => TypeCode.Object; TypeCode IConvertible.GetTypeCode() => TypeCode.Object;
...@@ -709,26 +719,23 @@ object IConvertible.ToType(Type conversionType, IFormatProvider provider) ...@@ -709,26 +719,23 @@ object IConvertible.ToType(Type conversionType, IFormatProvider provider)
/// <param name="val">The <see cref="long"/> value, if conversion was possible.</param> /// <param name="val">The <see cref="long"/> value, if conversion was possible.</param>
public bool TryParse(out long val) public bool TryParse(out long val)
{ {
switch(Type) switch (Type)
{
case CompareType.Int64: val =_overlappedValue64; return true;
case CompareType.String:
}
var blob = valueBlob;
if (blob == IntegerSentinel)
{
val = valueInt64;
return true;
}
if (blob == null)
{ {
// in redis-land 0 approx. equal null; so roll with it case StorageType.Null: val = 0; return true; // in redis-land 0 approx. equal null; so roll with it
val = 0; case StorageType.Int64: val = _overlappedValue64; return true;
return true; case StorageType.String: return TryParseInt64((string)_objectOrSentinel, out val);
case StorageType.Raw: return TryParseInt64(_memory.Span, out val);
case StorageType.Double:
var f64 = OverlappedValueDouble;
if (f64 >= long.MinValue && f64 <= long.MaxValue)
{
val = (long)f64;
return true;
}
break;
} }
val = default;
return TryParseInt64(blob, 0, blob.Length, out val); return false;
} }
/// <summary> /// <summary>
...@@ -739,14 +746,14 @@ public bool TryParse(out long val) ...@@ -739,14 +746,14 @@ public bool TryParse(out long val)
/// <param name="val">The <see cref="int"/> value, if conversion was possible.</param> /// <param name="val">The <see cref="int"/> value, if conversion was possible.</param>
public bool TryParse(out int val) public bool TryParse(out int val)
{ {
if (!TryParse(out long l) || l > int.MaxValue || l < int.MinValue) if (TryParse(out long l) && l >= int.MinValue && l <= int.MaxValue)
{ {
val = 0; val = (int)l;
return false; return true;
} }
val = default;
return false;
val = (int)l;
return true;
} }
/// <summary> /// <summary>
...@@ -757,20 +764,16 @@ public bool TryParse(out int val) ...@@ -757,20 +764,16 @@ public bool TryParse(out int val)
/// <param name="val">The <see cref="double"/> value, if conversion was possible.</param> /// <param name="val">The <see cref="double"/> value, if conversion was possible.</param>
public bool TryParse(out double val) public bool TryParse(out double val)
{ {
var blob = valueBlob; switch (Type)
if (blob == IntegerSentinel)
{
val = valueInt64;
return true;
}
if (blob == null)
{ {
// in redis-land 0 approx. equal null; so roll with it case StorageType.Null: val = 0; return true;
val = 0; case StorageType.Int64: val = _overlappedValue64; return true;
return true; case StorageType.Double: val = OverlappedValueDouble; return true;
case StorageType.String: return Format.TryParseDouble((string)_objectOrSentinel, out val);
case StorageType.Raw: return TryParseDouble(_memory.Span, out val);
} }
val = default;
return TryParseDouble(blob, out val); return false;
} }
} }
} }
...@@ -9,9 +9,6 @@ class Program ...@@ -9,9 +9,6 @@ class Program
{ {
static int Main() static int Main()
{ {
var options = PipeOptions.Default;
Console.WriteLine(options.PauseWriterThreshold);
Console.WriteLine(options.ResumeWriterThreshold);
var s = new StringWriter(); var s = new StringWriter();
try try
{ {
...@@ -38,8 +35,8 @@ static int Main() ...@@ -38,8 +35,8 @@ static int Main()
} }
finally finally
{ {
// Console.WriteLine(); Console.WriteLine();
// Console.WriteLine(s); //Console.WriteLine(s);
} }
} }
......
...@@ -6,13 +6,17 @@ ...@@ -6,13 +6,17 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Computername)'=='OCHO' or '$(Computername)'=='SKINK'">
<LocalReference>true</LocalReference>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj" /> <ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Computername)'=='OCHO'"> <ItemGroup Condition="'$(LocalReference)'=='true'">
<ProjectReference Include="..\..\Pipelines.Sockets.Unofficial\src\Pipelines.Sockets.Unofficial\Pipelines.Sockets.Unofficial.csproj" /> <ProjectReference Include="..\..\Pipelines.Sockets.Unofficial\src\Pipelines.Sockets.Unofficial\Pipelines.Sockets.Unofficial.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Computername)'!='OCHO'"> <ItemGroup Condition="'$(LocalReference)'!='true'">
<PackageReference Include="Pipelines.Sockets.Unofficial" Version="0.2.1-alpha.48" /> <PackageReference Include="Pipelines.Sockets.Unofficial" Version="0.2.1-alpha.48" />
</ItemGroup> </ItemGroup>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment