// 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.Runtime.InteropServices; using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Networking { public abstract class UvStreamHandle : UvHandle { private readonly static Libuv.uv_connection_cb _uv_connection_cb = (handle, status) => UvConnectionCb(handle, status); // Ref and out lamda params must be explicitly typed private readonly static Libuv.uv_alloc_cb _uv_alloc_cb = (IntPtr handle, int suggested_size, out Libuv.uv_buf_t buf) => UvAllocCb(handle, suggested_size, out buf); private readonly static Libuv.uv_read_cb _uv_read_cb = (IntPtr handle, int status, ref Libuv.uv_buf_t buf) => UvReadCb(handle, status, ref buf); private Action _listenCallback; private object _listenState; private GCHandle _listenVitality; private Func _allocCallback; private Action _readCallback; private object _readState; private GCHandle _readVitality; protected UvStreamHandle(IKestrelTrace logger) : base(logger) { } protected override bool ReleaseHandle() { if (_listenVitality.IsAllocated) { _listenVitality.Free(); } if (_readVitality.IsAllocated) { _readVitality.Free(); } return base.ReleaseHandle(); } public void Listen(int backlog, Action callback, object state) { if (_listenVitality.IsAllocated) { throw new InvalidOperationException("TODO: Listen may not be called more than once"); } try { _listenCallback = callback; _listenState = state; _listenVitality = GCHandle.Alloc(this, GCHandleType.Normal); _uv.listen(this, backlog, _uv_connection_cb); } catch { _listenCallback = null; _listenState = null; if (_listenVitality.IsAllocated) { _listenVitality.Free(); } throw; } } public void Accept(UvStreamHandle handle) { _uv.accept(this, handle); } public void ReadStart( Func allocCallback, Action readCallback, object state) { if (_readVitality.IsAllocated) { throw new InvalidOperationException("TODO: ReadStop must be called before ReadStart may be called again"); } try { _allocCallback = allocCallback; _readCallback = readCallback; _readState = state; _readVitality = GCHandle.Alloc(this, GCHandleType.Normal); _uv.read_start(this, _uv_alloc_cb, _uv_read_cb); } catch { _allocCallback = null; _readCallback = null; _readState = null; if (_readVitality.IsAllocated) { _readVitality.Free(); } throw; } } public void ReadStop() { if (!_readVitality.IsAllocated) { throw new InvalidOperationException("TODO: ReadStart must be called before ReadStop may be called"); } _allocCallback = null; _readCallback = null; _readState = null; _readVitality.Free(); _uv.read_stop(this); } public int TryWrite(Libuv.uv_buf_t buf) { return _uv.try_write(this, new[] { buf }, 1); } private static void UvConnectionCb(IntPtr handle, int status) { var stream = FromIntPtr(handle); Exception error; status = stream.Libuv.Check(status, out error); try { stream._listenCallback(stream, status, error, stream._listenState); } catch (Exception ex) { stream._log.LogError(0, ex, "UvConnectionCb"); throw; } } private static void UvAllocCb(IntPtr handle, int suggested_size, out Libuv.uv_buf_t buf) { var stream = FromIntPtr(handle); try { buf = stream._allocCallback(stream, suggested_size, stream._readState); } catch (Exception ex) { stream._log.LogError(0, ex, "UvAllocCb"); buf = stream.Libuv.buf_init(IntPtr.Zero, 0); throw; } } private static void UvReadCb(IntPtr handle, int status, ref Libuv.uv_buf_t buf) { var stream = FromIntPtr(handle); try { stream._readCallback(stream, status, stream._readState); } catch (Exception ex) { stream._log.LogError(0, ex, "UbReadCb"); throw; } } } }