don't allocate/copy a buffer in ReadMetadataFromPtrUnsafe unless we actually need to (move that logic to CreateUnsafe); implement well-known strings check (just "user-agent" at the moment)

pull/19511/head
mgravell 6 years ago
parent 264fca1eb6
commit e95f3297aa
  1. 3
      src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj
  2. 36
      src/csharp/Grpc.Core.Api/Metadata.cs
  3. 3
      src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
  4. 30
      src/csharp/Grpc.Core.Tests/MetadataTest.cs
  5. 47
      src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Grpc.Core\Common.csproj.include" />
@ -11,6 +11,7 @@
<PackageProjectUrl>https://github.com/grpc/grpc</PackageProjectUrl>
<PackageTags>gRPC RPC HTTP/2</PackageTags>
<VersionPrefix>$(GrpcCsharpVersion)</VersionPrefix>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>

@ -17,6 +17,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
@ -345,15 +346,44 @@ namespace Grpc.Core
/// Creates a binary value or ascii value metadata entry from data received from the native layer.
/// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying.
/// </summary>
internal static Entry CreateUnsafe(string key, byte[] valueBytes)
internal static unsafe Entry CreateUnsafe(string key, byte* source, int length)
{
if (HasBinaryHeaderSuffix(key))
{
return new Entry(key, null, valueBytes);
byte[] arr;
if (length == 0)
{
arr = EmptyBlob;
}
else
{ // create a local copy in a fresh array
arr = new byte[length];
Marshal.Copy(new IntPtr(source), arr, 0, length);
}
return new Entry(key, null, arr);
}
else
{
string s;
if (length == 0)
{
s = "";
}
else
{
int charCount = EncodingASCII.GetCharCount(source, length);
s = new string('\0', charCount);
fixed (char* cPtr = s)
{
EncodingASCII.GetChars(source, length, cPtr, charCount);
}
}
return new Entry(key, s, null);
}
return new Entry(key, EncodingASCII.GetString(valueBytes), null);
}
static readonly byte[] EmptyBlob = new byte[0];
private static string NormalizeKey(string key)
{
GrpcPreconditions.CheckNotNull(key, "key");

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Grpc.Core\Common.csproj.include" />
@ -6,6 +6,7 @@
<TargetFrameworks>net45;netcoreapp2.1</TargetFrameworks>
<OutputType>Exe</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">

@ -111,25 +111,31 @@ namespace Grpc.Core.Tests
}
[Test]
public void Entry_CreateUnsafe_Ascii()
public unsafe void Entry_CreateUnsafe_Ascii()
{
var bytes = new byte[] { (byte)'X', (byte)'y' };
var entry = Metadata.Entry.CreateUnsafe("abc", bytes);
Assert.IsFalse(entry.IsBinary);
Assert.AreEqual("abc", entry.Key);
Assert.AreEqual("Xy", entry.Value);
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
fixed (byte* ptr = bytes)
{
var entry = Metadata.Entry.CreateUnsafe("abc", ptr, bytes.Length);
Assert.IsFalse(entry.IsBinary);
Assert.AreEqual("abc", entry.Key);
Assert.AreEqual("Xy", entry.Value);
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
}
}
[Test]
public void Entry_CreateUnsafe_Binary()
public unsafe void Entry_CreateUnsafe_Binary()
{
var bytes = new byte[] { 1, 2, 3 };
var entry = Metadata.Entry.CreateUnsafe("abc-bin", bytes);
Assert.IsTrue(entry.IsBinary);
Assert.AreEqual("abc-bin", entry.Key);
Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
fixed (byte* ptr = bytes)
{
var entry = Metadata.Entry.CreateUnsafe("abc-bin", ptr, bytes.Length);
Assert.IsTrue(entry.IsBinary);
Assert.AreEqual("abc-bin", entry.Key);
Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
}
}
[Test]

@ -15,8 +15,7 @@
#endregion
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Grpc.Core.Profiling;
using System.Text;
namespace Grpc.Core.Internal
{
@ -51,7 +50,7 @@ namespace Grpc.Core.Internal
/// <summary>
/// Reads metadata from pointer to grpc_metadata_array
/// </summary>
public static Metadata ReadMetadataFromPtrUnsafe(IntPtr metadataArray)
public static unsafe Metadata ReadMetadataFromPtrUnsafe(IntPtr metadataArray)
{
if (metadataArray == IntPtr.Zero)
{
@ -66,16 +65,50 @@ namespace Grpc.Core.Internal
var index = new UIntPtr(i);
UIntPtr keyLen;
IntPtr keyPtr = Native.grpcsharp_metadata_array_get_key(metadataArray, index, out keyLen);
string key = Marshal.PtrToStringAnsi(keyPtr, (int)keyLen.ToUInt32());
int keyLen32 = checked((int)keyLen.ToUInt32());
string key = WellKnownStrings.TryIdentify((byte*)keyPtr.ToPointer(), keyLen32)
?? Marshal.PtrToStringAnsi(keyPtr, keyLen32);
UIntPtr valueLen;
IntPtr valuePtr = Native.grpcsharp_metadata_array_get_value(metadataArray, index, out valueLen);
var bytes = new byte[valueLen.ToUInt64()];
Marshal.Copy(valuePtr, bytes, 0, bytes.Length);
metadata.Add(Metadata.Entry.CreateUnsafe(key, bytes));
int len32 = checked((int)valueLen.ToUInt64());
metadata.Add(Metadata.Entry.CreateUnsafe(key, (byte*)valuePtr.ToPointer(), len32));
}
return metadata;
}
private static class WellKnownStrings
{
// turn a string of ASCII-length 8 into a ulong using the CPUs current
// endianness; this allows us to do the same thing in TryIdentify,
// testing string prefixes (of length >= 8) in a single load/compare
private static unsafe ulong Thunk8(string value)
{
int byteCount = Encoding.ASCII.GetByteCount(value);
if (byteCount != 8) throw new ArgumentException(nameof(value));
ulong result = 0;
fixed (char* cPtr = value)
{
Encoding.ASCII.GetBytes(cPtr, value.Length, (byte*)&result, byteCount);
}
return result;
}
private static readonly ulong UserAgent = Thunk8("user-age");
public static unsafe string TryIdentify(byte* source, int length)
{
switch (length)
{
case 10:
// test the first 8 bytes via evilness
ulong first8 = *(ulong*)source;
if (first8 == UserAgent & source[8] == (byte)'n' & source[9] == (byte)'t')
return "user-agent";
break;
}
return null;
}
}
internal IntPtr Handle
{
get

Loading…
Cancel
Save