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

@ -17,6 +17,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -345,14 +346,43 @@ namespace Grpc.Core
/// Creates a binary value or ascii value metadata entry from data received from the native layer. /// 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. /// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying.
/// </summary> /// </summary>
internal static Entry CreateUnsafe(string key, byte[] valueBytes) internal static unsafe Entry CreateUnsafe(string key, byte* source, int length)
{ {
if (HasBinaryHeaderSuffix(key)) 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, EncodingASCII.GetString(valueBytes), null);
} }
return new Entry(key, s, null);
}
}
static readonly byte[] EmptyBlob = new byte[0];
private static string NormalizeKey(string key) private static string NormalizeKey(string key)
{ {

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

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

@ -15,8 +15,7 @@
#endregion #endregion
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Text;
using Grpc.Core.Profiling;
namespace Grpc.Core.Internal namespace Grpc.Core.Internal
{ {
@ -51,7 +50,7 @@ namespace Grpc.Core.Internal
/// <summary> /// <summary>
/// Reads metadata from pointer to grpc_metadata_array /// Reads metadata from pointer to grpc_metadata_array
/// </summary> /// </summary>
public static Metadata ReadMetadataFromPtrUnsafe(IntPtr metadataArray) public static unsafe Metadata ReadMetadataFromPtrUnsafe(IntPtr metadataArray)
{ {
if (metadataArray == IntPtr.Zero) if (metadataArray == IntPtr.Zero)
{ {
@ -66,16 +65,50 @@ namespace Grpc.Core.Internal
var index = new UIntPtr(i); var index = new UIntPtr(i);
UIntPtr keyLen; UIntPtr keyLen;
IntPtr keyPtr = Native.grpcsharp_metadata_array_get_key(metadataArray, index, out 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; UIntPtr valueLen;
IntPtr valuePtr = Native.grpcsharp_metadata_array_get_value(metadataArray, index, out valueLen); IntPtr valuePtr = Native.grpcsharp_metadata_array_get_value(metadataArray, index, out valueLen);
var bytes = new byte[valueLen.ToUInt64()]; int len32 = checked((int)valueLen.ToUInt64());
Marshal.Copy(valuePtr, bytes, 0, bytes.Length); metadata.Add(Metadata.Entry.CreateUnsafe(key, (byte*)valuePtr.ToPointer(), len32));
metadata.Add(Metadata.Entry.CreateUnsafe(key, bytes));
} }
return metadata; 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 internal IntPtr Handle
{ {
get get

Loading…
Cancel
Save