// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; namespace Libuv.Internal { /// /// Summary description for UvWriteRequest /// public class UvWriteReq : UvRequest { private static readonly LibuvFunctions.uv_write_cb _uv_write_cb = (ptr, status) => UvWriteCb(ptr, status); private IntPtr _bufs; private Action _callback; private object _state; private const int BUFFER_COUNT = 4; private LibuvAwaitable _awaitable = new LibuvAwaitable(); private List _pins = new List(BUFFER_COUNT + 1); private List _handles = new List(BUFFER_COUNT + 1); public UvWriteReq(ILibuvTrace logger) : base(logger) { } public override void Init(LibuvThread thread) { DangerousInit(thread.Loop); base.Init(thread); } public void DangerousInit(UvLoopHandle loop) { var requestSize = loop.Libuv.req_size(LibuvFunctions.RequestType.WRITE); var bufferSize = Marshal.SizeOf() * BUFFER_COUNT; CreateMemory( loop.Libuv, loop.ThreadId, requestSize + bufferSize); _bufs = handle + requestSize; } public LibuvAwaitable WriteAsync(UvStreamHandle handle, in ReadOnlySequence buffer) { Write(handle, buffer, LibuvAwaitable.Callback, _awaitable); return _awaitable; } public LibuvAwaitable WriteAsync(UvStreamHandle handle, ArraySegment> bufs) { Write(handle, bufs, LibuvAwaitable.Callback, _awaitable); return _awaitable; } private unsafe void Write( UvStreamHandle handle, in ReadOnlySequence buffer, Action callback, object state) { try { var nBuffers = 0; if (buffer.IsSingleSegment) nBuffers = 1; else foreach (var _ in buffer) nBuffers++; var pBuffers = (LibuvFunctions.uv_buf_t*)_bufs; if (pBuffers == null) throw new NullReferenceException(); if (nBuffers > BUFFER_COUNT) { // create and pin buffer array when it's larger than the pre-allocated one var bufArray = new LibuvFunctions.uv_buf_t[nBuffers]; var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned); _pins.Add(gcHandle); pBuffers = (LibuvFunctions.uv_buf_t*)gcHandle.AddrOfPinnedObject(); if (pBuffers == null) throw new NullReferenceException(); } if (nBuffers == 1) { var memory = buffer.First; var memoryHandle = memory.Pin(); _handles.Add(memoryHandle); // Fast path for single buffer pBuffers[0] = Libuv.buf_init( (IntPtr)memoryHandle.Pointer, memory.Length); } else { var index = 0; foreach (var memory in buffer) { // This won't actually pin the buffer since we're already using pinned memory var memoryHandle = memory.Pin(); _handles.Add(memoryHandle); // create and pin each segment being written pBuffers[index] = Libuv.buf_init( (IntPtr)memoryHandle.Pointer, memory.Length); index++; } } _callback = callback; _state = state; _uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb); } catch { _callback = null; _state = null; UnpinGcHandles(); throw; } } private void Write( UvStreamHandle handle, ArraySegment> bufs, Action callback, object state) { WriteArraySegmentInternal(handle, bufs, null, callback, state); } public void Write2( UvStreamHandle handle, ArraySegment> bufs, UvStreamHandle sendHandle, Action callback, object state) { WriteArraySegmentInternal(handle, bufs, sendHandle, callback, state); } private unsafe void WriteArraySegmentInternal( UvStreamHandle handle, ArraySegment> bufs, UvStreamHandle sendHandle, Action callback, object state ) { try { var pBuffers = (LibuvFunctions.uv_buf_t*)_bufs; if (pBuffers == null) throw new NullReferenceException(); var nBuffers = bufs.Count; if (nBuffers > BUFFER_COUNT) { // create and pin buffer array when it's larger than the pre-allocated one var bufArray = new LibuvFunctions.uv_buf_t[nBuffers]; var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned); _pins.Add(gcHandle); pBuffers = (LibuvFunctions.uv_buf_t*)gcHandle.AddrOfPinnedObject(); if (pBuffers == null) throw new NullReferenceException(); } for (var index = 0; index < nBuffers; index++) { // create and pin each segment being written var buf = bufs.Array?[bufs.Offset + index] ?? throw new Exception("buffs.Array is null"); var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned); _pins.Add(gcHandle); pBuffers[index] = Libuv.buf_init( gcHandle.AddrOfPinnedObject() + buf.Offset, buf.Count); } _callback = callback; _state = state; if (sendHandle == null) _uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb); else _uv.write2(this, handle, pBuffers, nBuffers, sendHandle, _uv_write_cb); } catch { _callback = null; _state = null; UnpinGcHandles(); throw; } } // Safe handle has instance method called Unpin // so using UnpinGcHandles to avoid conflict private void UnpinGcHandles() { var pinList = _pins; var count = pinList.Count; for (var i = 0; i < count; i++) pinList[i].Free(); pinList.Clear(); var handleList = _handles; count = handleList.Count; for (var i = 0; i < count; i++) handleList[i].Dispose(); handleList.Clear(); } private static void UvWriteCb(IntPtr ptr, int status) { var req = FromIntPtr(ptr); req.UnpinGcHandles(); var callback = req._callback; req._callback = null; var state = req._state; req._state = null; UvException error = null; if (status < 0) req.Libuv.Check(status, out error); try { callback(req, status, error, state); } catch (Exception ex) { req._log.LogError(0, ex, "UvWriteCb"); throw; } } } }