mirror of https://github.com/grpc/grpc.git
commit
7ec1a9663d
119 changed files with 2258 additions and 2314 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,207 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2019 The gRPC Authors |
||||
// |
||||
// Licensed 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. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Internal; |
||||
using Grpc.Core.Utils; |
||||
using NUnit.Framework; |
||||
|
||||
namespace Grpc.Core.Internal.Tests |
||||
{ |
||||
public class DefaultSerializationContextTest |
||||
{ |
||||
[TestCase] |
||||
public void CompleteAllowedOnlyOnce() |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
var buffer = GetTestBuffer(10); |
||||
|
||||
context.Complete(buffer); |
||||
Assert.Throws(typeof(InvalidOperationException), () => context.Complete(buffer)); |
||||
Assert.Throws(typeof(InvalidOperationException), () => context.Complete()); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void CompleteAllowedOnlyOnce2() |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
|
||||
context.Complete(); |
||||
Assert.Throws(typeof(InvalidOperationException), () => context.Complete(GetTestBuffer(10))); |
||||
Assert.Throws(typeof(InvalidOperationException), () => context.Complete()); |
||||
} |
||||
} |
||||
|
||||
[TestCase(0)] |
||||
[TestCase(1)] |
||||
[TestCase(10)] |
||||
[TestCase(100)] |
||||
[TestCase(1000)] |
||||
public void ByteArrayPayload(int payloadSize) |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
var origPayload = GetTestBuffer(payloadSize); |
||||
|
||||
context.Complete(origPayload); |
||||
|
||||
var nativePayload = context.GetPayload().ToByteArray(); |
||||
CollectionAssert.AreEqual(origPayload, nativePayload); |
||||
} |
||||
} |
||||
|
||||
[TestCase(0)] |
||||
[TestCase(1)] |
||||
[TestCase(10)] |
||||
[TestCase(100)] |
||||
[TestCase(1000)] |
||||
public void BufferWriter_OneSegment(int payloadSize) |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
var origPayload = GetTestBuffer(payloadSize); |
||||
|
||||
var bufferWriter = context.GetBufferWriter(); |
||||
origPayload.AsSpan().CopyTo(bufferWriter.GetSpan(payloadSize)); |
||||
bufferWriter.Advance(payloadSize); |
||||
context.Complete(); |
||||
|
||||
var nativePayload = context.GetPayload().ToByteArray(); |
||||
CollectionAssert.AreEqual(origPayload, nativePayload); |
||||
} |
||||
} |
||||
|
||||
[TestCase(0)] |
||||
[TestCase(1)] |
||||
[TestCase(10)] |
||||
[TestCase(100)] |
||||
[TestCase(1000)] |
||||
public void BufferWriter_OneSegment_GetMemory(int payloadSize) |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
var origPayload = GetTestBuffer(payloadSize); |
||||
|
||||
var bufferWriter = context.GetBufferWriter(); |
||||
origPayload.AsSpan().CopyTo(bufferWriter.GetMemory(payloadSize).Span); |
||||
bufferWriter.Advance(payloadSize); |
||||
context.Complete(); |
||||
|
||||
var nativePayload = context.GetPayload().ToByteArray(); |
||||
CollectionAssert.AreEqual(origPayload, nativePayload); |
||||
} |
||||
} |
||||
|
||||
[TestCase(1, 4)] // small slice size tests grpc_slice with inline data |
||||
[TestCase(10, 4)] |
||||
[TestCase(100, 4)] |
||||
[TestCase(1000, 4)] |
||||
[TestCase(1, 64)] // larger slice size tests allocated grpc_slices |
||||
[TestCase(10, 64)] |
||||
[TestCase(1000, 50)] |
||||
[TestCase(1000, 64)] |
||||
public void BufferWriter_MultipleSegments(int payloadSize, int maxSliceSize) |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
var origPayload = GetTestBuffer(payloadSize); |
||||
|
||||
var bufferWriter = context.GetBufferWriter(); |
||||
for (int offset = 0; offset < payloadSize; offset += maxSliceSize) |
||||
{ |
||||
var sliceSize = Math.Min(maxSliceSize, payloadSize - offset); |
||||
// we allocate last slice as too big intentionally to test that shrinking works |
||||
var dest = bufferWriter.GetSpan(maxSliceSize); |
||||
|
||||
origPayload.AsSpan(offset, sliceSize).CopyTo(dest); |
||||
bufferWriter.Advance(sliceSize); |
||||
} |
||||
context.Complete(); |
||||
|
||||
var nativePayload = context.GetPayload().ToByteArray(); |
||||
CollectionAssert.AreEqual(origPayload, nativePayload); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void ContextIsReusable() |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
|
||||
Assert.Throws(typeof(NullReferenceException), () => context.GetPayload()); |
||||
|
||||
var origPayload1 = GetTestBuffer(10); |
||||
context.Complete(origPayload1); |
||||
CollectionAssert.AreEqual(origPayload1, context.GetPayload().ToByteArray()); |
||||
|
||||
context.Reset(); |
||||
|
||||
var origPayload2 = GetTestBuffer(20); |
||||
|
||||
var bufferWriter = context.GetBufferWriter(); |
||||
origPayload2.AsSpan().CopyTo(bufferWriter.GetMemory(origPayload2.Length).Span); |
||||
bufferWriter.Advance(origPayload2.Length); |
||||
context.Complete(); |
||||
CollectionAssert.AreEqual(origPayload2, context.GetPayload().ToByteArray()); |
||||
|
||||
context.Reset(); |
||||
|
||||
Assert.Throws(typeof(NullReferenceException), () => context.GetPayload()); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void GetBufferWriterThrowsForCompletedContext() |
||||
{ |
||||
using (var scope = NewDefaultSerializationContextScope()) |
||||
{ |
||||
var context = scope.Context; |
||||
context.Complete(GetTestBuffer(10)); |
||||
|
||||
Assert.Throws(typeof(InvalidOperationException), () => context.GetBufferWriter()); |
||||
} |
||||
} |
||||
|
||||
private DefaultSerializationContext.UsageScope NewDefaultSerializationContextScope() |
||||
{ |
||||
return new DefaultSerializationContext.UsageScope(new DefaultSerializationContext()); |
||||
} |
||||
|
||||
private byte[] GetTestBuffer(int length) |
||||
{ |
||||
var testBuffer = new byte[length]; |
||||
for (int i = 0; i < testBuffer.Length; i++) |
||||
{ |
||||
testBuffer[i] = (byte) i; |
||||
} |
||||
return testBuffer; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,163 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2019 The gRPC Authors |
||||
// |
||||
// Licensed 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. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Internal; |
||||
using Grpc.Core.Utils; |
||||
using NUnit.Framework; |
||||
|
||||
namespace Grpc.Core.Internal.Tests |
||||
{ |
||||
public class SliceBufferSafeHandleTest |
||||
{ |
||||
[TestCase] |
||||
public void Complete_EmptyBuffer() |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
sliceBuffer.Complete(); |
||||
CollectionAssert.AreEqual(new byte[0], sliceBuffer.ToByteArray()); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void Complete_TailSizeZero() |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
var origPayload = GetTestBuffer(10); |
||||
origPayload.AsSpan().CopyTo(sliceBuffer.GetSpan(origPayload.Length)); |
||||
sliceBuffer.Advance(origPayload.Length); |
||||
// call complete where tail space size == 0 |
||||
sliceBuffer.Complete(); |
||||
CollectionAssert.AreEqual(origPayload, sliceBuffer.ToByteArray()); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void Complete_TruncateTailSpace() |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
var origPayload = GetTestBuffer(10); |
||||
var dest = sliceBuffer.GetSpan(origPayload.Length + 10); |
||||
origPayload.AsSpan().CopyTo(dest); |
||||
sliceBuffer.Advance(origPayload.Length); |
||||
// call complete where tail space needs to be truncated |
||||
sliceBuffer.Complete(); |
||||
CollectionAssert.AreEqual(origPayload, sliceBuffer.ToByteArray()); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void SliceBufferIsReusable() |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
var origPayload = GetTestBuffer(10); |
||||
origPayload.AsSpan().CopyTo(sliceBuffer.GetSpan(origPayload.Length)); |
||||
sliceBuffer.Advance(origPayload.Length); |
||||
sliceBuffer.Complete(); |
||||
CollectionAssert.AreEqual(origPayload, sliceBuffer.ToByteArray()); |
||||
|
||||
sliceBuffer.Reset(); |
||||
|
||||
var origPayload2 = GetTestBuffer(20); |
||||
origPayload2.AsSpan().CopyTo(sliceBuffer.GetSpan(origPayload2.Length)); |
||||
sliceBuffer.Advance(origPayload2.Length); |
||||
sliceBuffer.Complete(); |
||||
CollectionAssert.AreEqual(origPayload2, sliceBuffer.ToByteArray()); |
||||
|
||||
sliceBuffer.Reset(); |
||||
|
||||
CollectionAssert.AreEqual(new byte[0], sliceBuffer.ToByteArray()); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void SliceBuffer_SizeHintZero() |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
var destSpan = sliceBuffer.GetSpan(0); |
||||
Assert.IsTrue(destSpan.Length > 0); // some non-zero size memory is made available |
||||
|
||||
sliceBuffer.Reset(); |
||||
|
||||
var destMemory = sliceBuffer.GetMemory(0); |
||||
Assert.IsTrue(destMemory.Length > 0); |
||||
} |
||||
} |
||||
|
||||
[TestCase(0)] |
||||
[TestCase(1000)] |
||||
public void SliceBuffer_BigPayload(int sizeHint) |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
var bigPayload = GetTestBuffer(4 * 1024 * 1024); |
||||
|
||||
int offset = 0; |
||||
while (offset < bigPayload.Length) |
||||
{ |
||||
var destSpan = sliceBuffer.GetSpan(sizeHint); |
||||
int copySize = Math.Min(destSpan.Length, bigPayload.Length - offset); |
||||
bigPayload.AsSpan(offset, copySize).CopyTo(destSpan); |
||||
sliceBuffer.Advance(copySize); |
||||
offset += copySize; |
||||
} |
||||
|
||||
sliceBuffer.Complete(); |
||||
CollectionAssert.AreEqual(bigPayload, sliceBuffer.ToByteArray()); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void SliceBuffer_NegativeSizeHint() |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
Assert.Throws(typeof(ArgumentException), () => sliceBuffer.GetSpan(-1)); |
||||
Assert.Throws(typeof(ArgumentException), () => sliceBuffer.GetMemory(-1)); |
||||
} |
||||
} |
||||
|
||||
[TestCase] |
||||
public void SliceBuffer_AdvanceBadArg() |
||||
{ |
||||
using (var sliceBuffer = SliceBufferSafeHandle.Create()) |
||||
{ |
||||
int size = 10; |
||||
var destSpan = sliceBuffer.GetSpan(size); |
||||
Assert.Throws(typeof(ArgumentException), () => sliceBuffer.Advance(size + 1)); |
||||
Assert.Throws(typeof(ArgumentException), () => sliceBuffer.Advance(-1)); |
||||
} |
||||
} |
||||
|
||||
private byte[] GetTestBuffer(int length) |
||||
{ |
||||
var testBuffer = new byte[length]; |
||||
for (int i = 0; i < testBuffer.Length; i++) |
||||
{ |
||||
testBuffer[i] = (byte) i; |
||||
} |
||||
return testBuffer; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,166 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2019 The gRPC Authors |
||||
// |
||||
// Licensed 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. |
||||
|
||||
#endregion |
||||
|
||||
using System; |
||||
using System.Buffers; |
||||
using System.Runtime.InteropServices; |
||||
using Grpc.Core; |
||||
using Grpc.Core.Logging; |
||||
using Grpc.Core.Utils; |
||||
|
||||
namespace Grpc.Core.Internal |
||||
{ |
||||
/// <summary> |
||||
/// Represents grpc_slice_buffer with some extra utility functions to allow |
||||
/// writing data to it using the <c>IBufferWriter</c> interface. |
||||
/// </summary> |
||||
internal class SliceBufferSafeHandle : SafeHandleZeroIsInvalid, IBufferWriter<byte> |
||||
{ |
||||
const int DefaultTailSpaceSize = 4096; // default buffer to allocate if no size hint is provided |
||||
static readonly NativeMethods Native = NativeMethods.Get(); |
||||
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<SliceBufferSafeHandle>(); |
||||
|
||||
public static readonly SliceBufferSafeHandle NullInstance = new SliceBufferSafeHandle(); |
||||
|
||||
private IntPtr tailSpacePtr; |
||||
private int tailSpaceLen; |
||||
|
||||
private SliceMemoryManager memoryManagerLazy; |
||||
|
||||
private SliceBufferSafeHandle() |
||||
{ |
||||
} |
||||
|
||||
public static SliceBufferSafeHandle Create() |
||||
{ |
||||
return Native.grpcsharp_slice_buffer_create(); |
||||
} |
||||
|
||||
public IntPtr Handle |
||||
{ |
||||
get |
||||
{ |
||||
return handle; |
||||
} |
||||
} |
||||
|
||||
public void Advance(int count) |
||||
{ |
||||
GrpcPreconditions.CheckArgument(count >= 0); |
||||
GrpcPreconditions.CheckArgument(tailSpacePtr != IntPtr.Zero || count == 0); |
||||
GrpcPreconditions.CheckArgument(tailSpaceLen >= count); |
||||
tailSpaceLen = tailSpaceLen - count; |
||||
tailSpacePtr += count; |
||||
memoryManagerLazy?.Reset(); |
||||
} |
||||
|
||||
// provides access to the "tail space" of this buffer. |
||||
// Use GetSpan when possible for better efficiency. |
||||
public Memory<byte> GetMemory(int sizeHint = 0) |
||||
{ |
||||
EnsureBufferSpace(sizeHint); |
||||
if (memoryManagerLazy == null) |
||||
{ |
||||
memoryManagerLazy = new SliceMemoryManager(); |
||||
} |
||||
memoryManagerLazy.Reset(new Slice(tailSpacePtr, tailSpaceLen)); |
||||
return memoryManagerLazy.Memory; |
||||
} |
||||
|
||||
// provides access to the "tail space" of this buffer. |
||||
public unsafe Span<byte> GetSpan(int sizeHint = 0) |
||||
{ |
||||
EnsureBufferSpace(sizeHint); |
||||
return new Span<byte>(tailSpacePtr.ToPointer(), tailSpaceLen); |
||||
} |
||||
|
||||
public void Complete() |
||||
{ |
||||
AdjustTailSpace(0); |
||||
} |
||||
|
||||
// resets the data contained by this slice buffer |
||||
public void Reset() |
||||
{ |
||||
// deletes all the data in the slice buffer |
||||
tailSpacePtr = IntPtr.Zero; |
||||
tailSpaceLen = 0; |
||||
memoryManagerLazy?.Reset(); |
||||
Native.grpcsharp_slice_buffer_reset_and_unref(this); |
||||
} |
||||
|
||||
// copies the content of the slice buffer to a newly allocated byte array |
||||
// Note that this method has a relatively high overhead and should maily be used for testing. |
||||
public byte[] ToByteArray() |
||||
{ |
||||
ulong sliceCount = Native.grpcsharp_slice_buffer_slice_count(this).ToUInt64(); |
||||
|
||||
Slice[] slices = new Slice[sliceCount]; |
||||
int totalLen = 0; |
||||
for (int i = 0; i < (int) sliceCount; i++) |
||||
{ |
||||
Native.grpcsharp_slice_buffer_slice_peek(this, new UIntPtr((ulong) i), out UIntPtr sliceLen, out IntPtr dataPtr); |
||||
slices[i] = new Slice(dataPtr, (int) sliceLen.ToUInt64()); |
||||
totalLen += (int) sliceLen.ToUInt64(); |
||||
|
||||
} |
||||
var result = new byte[totalLen]; |
||||
int offset = 0; |
||||
for (int i = 0; i < (int) sliceCount; i++) |
||||
{ |
||||
slices[i].ToSpanUnsafe().CopyTo(result.AsSpan(offset, slices[i].Length)); |
||||
offset += slices[i].Length; |
||||
} |
||||
GrpcPreconditions.CheckState(totalLen == offset); |
||||
return result; |
||||
} |
||||
|
||||
private void EnsureBufferSpace(int sizeHint) |
||||
{ |
||||
GrpcPreconditions.CheckArgument(sizeHint >= 0); |
||||
if (sizeHint == 0) |
||||
{ |
||||
// if no hint is provided, keep the available space within some "reasonable" boundaries. |
||||
// This is quite a naive approach which could use some fine-tuning, but currently in most case we know |
||||
// the required buffer size in advance anyway, so this approach seems good enough for now. |
||||
if (tailSpaceLen < DefaultTailSpaceSize / 2) |
||||
{ |
||||
AdjustTailSpace(DefaultTailSpaceSize); |
||||
} |
||||
} |
||||
else if (tailSpaceLen < sizeHint) |
||||
{ |
||||
// if hint is provided, always make sure we provide at least that much space |
||||
AdjustTailSpace(sizeHint); |
||||
} |
||||
} |
||||
|
||||
// make sure there's exactly requestedSize bytes of continguous buffer space at the end of this slice buffer |
||||
private void AdjustTailSpace(int requestedSize) |
||||
{ |
||||
GrpcPreconditions.CheckArgument(requestedSize >= 0); |
||||
tailSpacePtr = Native.grpcsharp_slice_buffer_adjust_tail_space(this, new UIntPtr((ulong) tailSpaceLen), new UIntPtr((ulong) requestedSize)); |
||||
tailSpaceLen = requestedSize; |
||||
} |
||||
protected override bool ReleaseHandle() |
||||
{ |
||||
Native.grpcsharp_slice_buffer_destroy(handle); |
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,65 @@ |
||||
#region Copyright notice and license |
||||
|
||||
// Copyright 2019 The gRPC Authors |
||||
// |
||||
// Licensed 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. |
||||
|
||||
#endregion |
||||
|
||||
using Grpc.Core.Utils; |
||||
using System; |
||||
using System.Threading; |
||||
|
||||
using System.Buffers; |
||||
|
||||
namespace Grpc.Core.Internal |
||||
{ |
||||
// Allow creating instances of Memory<byte> from Slice. |
||||
// Represents a chunk of native memory, but doesn't manage its lifetime. |
||||
// Instances of this class are reuseable - they can be reset to point to a different memory chunk. |
||||
// That is important to make the instances cacheable (rather then creating new instances |
||||
// the old ones will be reused to reduce GC pressure). |
||||
internal class SliceMemoryManager : MemoryManager<byte> |
||||
{ |
||||
private Slice slice; |
||||
|
||||
public void Reset(Slice slice) |
||||
{ |
||||
this.slice = slice; |
||||
} |
||||
|
||||
public void Reset() |
||||
{ |
||||
Reset(new Slice(IntPtr.Zero, 0)); |
||||
} |
||||
|
||||
public override Span<byte> GetSpan() |
||||
{ |
||||
return slice.ToSpanUnsafe(); |
||||
} |
||||
|
||||
public override MemoryHandle Pin(int elementIndex = 0) |
||||
{ |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
public override void Unpin() |
||||
{ |
||||
} |
||||
|
||||
protected override void Dispose(bool disposing) |
||||
{ |
||||
// NOP |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2019 gRPC authors. |
||||
* |
||||
* Licensed 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. |
||||
* |
||||
*/ |
||||
|
||||
#include <ruby/ruby.h> |
||||
|
||||
// This is a dummy C++ source file to trigger ruby extension builder to
|
||||
// pick C++ rather than C linker to link with c++ library properly.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue