From ee1f7dc33d3519276758cb0e5c4d38f89f9936e7 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 17 Oct 2018 14:42:29 +0200 Subject: [PATCH] [CoreFoundation] Implement missing dispatch API. Fixes #4606. (#4967) * [CoreFoundation] Add DispatchQueue.DispatchBarrierSync. * [CoreFoundation] Bind dispatch_queue_[set|get]_specific. * [CoreFoundation] Bind dispatch_queue_get_qos_class. * [CoreFoundation] Bind dispatch_queue_create_with_target. * Add tests. * Update xtro. * Add missing availability attributes. * [tests] Do a version check before testing new API. * Remove redundant code. --- src/CoreFoundation/Dispatch.cs | 181 +++++++++++++++++- .../CoreFoundation/DispatchQueueTest.cs | 124 ++++++++++++ .../xtro-sharpie/common-CoreFoundation.ignore | 9 + 3 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 tests/monotouch-test/CoreFoundation/DispatchQueueTest.cs diff --git a/src/CoreFoundation/Dispatch.cs b/src/CoreFoundation/Dispatch.cs index 4538525de10d..4882d9132cf2 100644 --- a/src/CoreFoundation/Dispatch.cs +++ b/src/CoreFoundation/Dispatch.cs @@ -47,6 +47,17 @@ public enum DispatchQueuePriority : int { Low = -2, Background = Int16.MinValue } + + // dispatch_qos_class_t is defined in usr/include/dispatch/queue.h, but redirects to qos_class_t + // the qos_class_t enum is defined in usr/include/sys/qos.h (typed as 'unsigned int') + public enum DispatchQualityOfService : uint { + UserInteractive = 0x21, + UserInitiated = 0x19, + Default = 0x15, + Utility = 0x11, + Background = 0x09, + Unspecified = 0x00, + } public abstract class DispatchObject : NativeObject { @@ -140,6 +151,22 @@ public void SetTargetQueue (DispatchQueue queue) [DllImport (Constants.libcLibrary)] internal extern static void dispatch_suspend (IntPtr o); + + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + public void Activate () + { + dispatch_activate (GetCheckedHandle ()); + } + + [DllImport (Constants.libcLibrary)] + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + extern static void dispatch_activate (/* dispatch_object_t */ IntPtr @object); #endif // !COREBUILD } @@ -177,6 +204,15 @@ public DispatchQueue (string label, bool concurrent) throw new Exception ("Error creating dispatch queue"); } + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + public DispatchQueue (string label, Attributes attributes, DispatchQueue target = null) + : base (dispatch_queue_create_with_target (label, attributes?.Create () ?? IntPtr.Zero, target.GetHandle ()), true) + { + } + // // Properties and methods // @@ -320,6 +356,14 @@ static void static_dispatcher_iterations_to_managed (IntPtr context, IntPtr coun } + internal static readonly dispatch_callback_t free_gchandle = static_free_gchandle; + + [MonoPInvokeCallback (typeof (dispatch_callback_t))] + static void static_free_gchandle (IntPtr context) + { + GCHandle.FromIntPtr (context).Free (); + } + public void DispatchAsync (Action action) { if (action == null) @@ -343,7 +387,15 @@ public void DispatchBarrierAsync (Action action) dispatch_barrier_async_f (Handle, (IntPtr) GCHandle.Alloc (Tuple.Create (action, this)), static_dispatch); } - + + public void DispatchBarrierSync (Action action) + { + if (action == null) + throw new ArgumentNullException (nameof (action)); + + dispatch_barrier_sync_f (Handle, (IntPtr) GCHandle.Alloc (Tuple.Create (action, this)), static_dispatch); + } + public void DispatchAfter (DispatchTime when, Action action) { if (action == null) @@ -359,12 +411,50 @@ public void Submit (Action action, long times) dispatch_apply_f ((IntPtr) times, Handle, (IntPtr) GCHandle.Alloc (Tuple.Create (action, this)), static_dispatch_iterations); } + public void SetSpecific (IntPtr key, object context) + { + dispatch_queue_set_specific (GetCheckedHandle (), key, (IntPtr) GCHandle.Alloc (context), free_gchandle); + } + + public object GetSpecific (IntPtr key) + { + GCHandle gchandle = (GCHandle) dispatch_queue_get_specific (GetCheckedHandle (), key); + return gchandle.Target; + } + + [Mac (10,10)] + [iOS (8,0)] + public DispatchQualityOfService GetQualityOfService (out int relative_priority) + { + unsafe { + fixed (int* rel_pri = &relative_priority) + return dispatch_queue_get_qos_class (Handle, rel_pri); + } + } + + [Mac (10,10)] + [iOS (8,0)] + public DispatchQualityOfService QualityOfService { + get { + unsafe { + return dispatch_queue_get_qos_class (Handle, null); + } + } + } + // // Native methods // [DllImport (Constants.libcLibrary)] extern static IntPtr dispatch_queue_create (string label, IntPtr attr); + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + [DllImport (Constants.libcLibrary)] + extern static IntPtr dispatch_queue_create_with_target (string label, IntPtr attr, IntPtr target); + [DllImport (Constants.libcLibrary)] extern static void dispatch_async_f (IntPtr queue, IntPtr context, dispatch_callback_t dispatch); @@ -374,6 +464,9 @@ public void Submit (Action action, long times) [DllImport (Constants.libcLibrary)] extern static void dispatch_barrier_async_f (IntPtr queue, IntPtr context, dispatch_callback_t dispatch); + [DllImport(Constants.libcLibrary)] + extern static void dispatch_barrier_sync_f (IntPtr queue, IntPtr context, dispatch_callback_t dispatch); + [DllImport (Constants.libcLibrary)] extern static void dispatch_after_f (/* dispath_time_t */ ulong time, IntPtr queue, IntPtr context, dispatch_callback_t dispatch); @@ -388,6 +481,17 @@ public void Submit (Action action, long times) // this returns a "const char*" so we cannot make a string out of it since it will be freed (and crash) extern static IntPtr dispatch_queue_get_label (IntPtr queue); + [DllImport(Constants.libcLibrary)] + extern static void dispatch_queue_set_specific (IntPtr queue, /* const void* */ IntPtr key, /* void *_Nullable */ IntPtr context, dispatch_callback_t /* _Nullable */ destructor); + + [DllImport(Constants.libcLibrary)] + extern static IntPtr dispatch_queue_get_specific (IntPtr queue, /* const void* */ IntPtr key); + + [Mac (10,10)] + [iOS (8,0)] + [DllImport (Constants.libcLibrary)] + unsafe extern static /* dispatch_qos_class_t */ DispatchQualityOfService dispatch_queue_get_qos_class (/* dispatch_queue_t */ IntPtr queue, /* int *_Nullable */ int* relative_priority); + public override bool Equals (object other) { DispatchQueue o = other as DispatchQueue; @@ -428,6 +532,81 @@ public static void MainIteration () dispatch_main (); } #endif + + public class Attributes + { + public bool Concurrent { get; set; } + + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + public bool IsInitiallyInactive { get; set; } + + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + public AutoreleaseFrequency? AutoreleaseFrequency { get; set; } + + [Mac (10,10)] + [iOS (8,0)] + public int RelativePriority { get; set; } + + [Mac (10,10)] + [iOS (8,0)] + public DispatchQualityOfService? QualityOfService { get; set; } + + internal IntPtr Create () + { + IntPtr rv = IntPtr.Zero; + + if (Concurrent) + rv = DispatchQueue.ConcurrentQueue; + + if (IsInitiallyInactive) + rv = dispatch_queue_attr_make_initially_inactive (rv); + + if (AutoreleaseFrequency.HasValue) + rv = dispatch_queue_attr_make_with_autorelease_frequency (rv, (nuint) (ulong) AutoreleaseFrequency.Value); + + if (QualityOfService.HasValue) + rv = dispatch_queue_attr_make_with_qos_class (rv, QualityOfService.Value, RelativePriority); + + return rv; + } + + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + [DllImport (Constants.libcLibrary)] + static extern /* dispatch_queue_attr_t */ IntPtr dispatch_queue_attr_make_initially_inactive (/* dispatch_queue_attr_t _Nullable */ IntPtr attr); + + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + [DllImport (Constants.libcLibrary)] + static extern /* dispatch_queue_attr_t */ IntPtr dispatch_queue_attr_make_with_autorelease_frequency (/* dispatch_queue_attr_t _Nullable */ IntPtr attr, /* dispatch_autorelease_frequency_t */ nuint frequency); + + [Mac (10,10)] + [iOS (8,0)] + [DllImport (Constants.libcLibrary)] + static extern /* dispatch_queue_attr_t */ IntPtr dispatch_queue_attr_make_with_qos_class (/* dispatch_queue_attr_t _Nullable */ IntPtr attr, /* dispatch_qos_class_t */ DispatchQualityOfService qos_class, int relative_priority); + } + + [Mac (10,12)] + [iOS (10,0)] + [TV (10,0)] + [Watch (3,0)] + [Native] + public enum AutoreleaseFrequency : ulong /* unsigned long */ + { + Inherit = 0, + WorkItem = 1, + Never = 2, + } #endif // !COREBUILD } diff --git a/tests/monotouch-test/CoreFoundation/DispatchQueueTest.cs b/tests/monotouch-test/CoreFoundation/DispatchQueueTest.cs new file mode 100644 index 000000000000..a7b7e87d9469 --- /dev/null +++ b/tests/monotouch-test/CoreFoundation/DispatchQueueTest.cs @@ -0,0 +1,124 @@ +// +// Unit tests for DispatchQueue +// +// Authors: +// Rolf Bjarne Kvinge +// +// Copyright 2018 Microsoft Corp. All rights reserved. +// + +using System; +using System.IO; +#if XAMCORE_2_0 +using CoreFoundation; +using Foundation; +using ObjCRuntime; +#if MONOMAC +using AppKit; +#else +using UIKit; +#endif +#else +using MonoTouch.CoreFoundation; +using MonoTouch.Foundation; +using MonoTouch.ObjCRuntime; +using MonoTouch.UIKit; +#endif +using NUnit.Framework; +using System.Drawing; +using System.Threading; + +#if XAMCORE_2_0 +using RectangleF = CoreGraphics.CGRect; +using SizeF = CoreGraphics.CGSize; +using PointF = CoreGraphics.CGPoint; +#else +using nfloat=global::System.Single; +using nint=global::System.Int32; +using nuint=global::System.UInt32; +#endif + +namespace MonoTouchFixtures.CoreFoundation +{ + + [TestFixture] + [Preserve(AllMembers = true)] + public class DispatchQueueTests + { + [Test] + public void CtorWithAttributes () + { + TestRuntime.AssertXcodeVersion (8, 0); + + using (var queue = new DispatchQueue ("1", new DispatchQueue.Attributes + { + AutoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.Inherit, + })) + { + Assert.AreNotEqual (IntPtr.Zero, queue.Handle, "Handle 1"); + } + + using (var queue = new DispatchQueue ("2", new DispatchQueue.Attributes + { + IsInitiallyInactive = true, + })) + { + queue.Activate (); // must activate the queue before it can be released according to Apple's documentation + Assert.AreNotEqual (IntPtr.Zero, queue.Handle, "Handle 2"); + } + + using (var queue = new DispatchQueue ("3", new DispatchQueue.Attributes + { + QualityOfService = DispatchQualityOfService.Utility, + })) + { + Assert.AreNotEqual (IntPtr.Zero, queue.Handle, "Handle 3"); + Assert.AreEqual (DispatchQualityOfService.Utility, queue.QualityOfService, "QualityOfService 3"); + } + + using (var target_queue = new DispatchQueue ("4 - target")) { + using (var queue = new DispatchQueue ("4", new DispatchQueue.Attributes + { + QualityOfService = DispatchQualityOfService.Background, + AutoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.WorkItem, + RelativePriority = -1, + }, target_queue)) + { + Assert.AreNotEqual (IntPtr.Zero, queue.Handle, "Handle 4"); + Assert.AreEqual (DispatchQualityOfService.Background, queue.GetQualityOfService (out var relative_priority), "QualityOfService 4"); + Assert.AreEqual (-1, relative_priority, "RelativePriority 4"); + } + } + } + + [Test] + public void Specific () + { + using (var queue = new DispatchQueue ("Specific")) + { + var key = (IntPtr) 0x31415926; + queue.SetSpecific (key, "hello world"); + Assert.AreEqual ("hello world", queue.GetSpecific (key), "Key"); + } + } + + [Test] + public void DispatchBarrierSync () + { + using (var queue = new DispatchQueue ("DispatchBarrierSync")) { + var called = false; + queue.DispatchBarrierSync(() => + { + called = true; + }); + Assert.IsTrue (called, "Called"); + } + } + + [Test] + public void MainQueue () + { + Assert.AreEqual (DispatchQueue.CurrentQueue, DispatchQueue.MainQueue, "MainQueue"); + } + } +} diff --git a/tests/xtro-sharpie/common-CoreFoundation.ignore b/tests/xtro-sharpie/common-CoreFoundation.ignore index 834c51367967..14643d8404f1 100644 --- a/tests/xtro-sharpie/common-CoreFoundation.ignore +++ b/tests/xtro-sharpie/common-CoreFoundation.ignore @@ -931,10 +931,12 @@ !missing-pinvoke! CFWriteStreamGetTypeID is not bound !unknown-field! _dispatch_data_destructor_free bound !unknown-pinvoke! close bound +!unknown-pinvoke! dispatch_activate bound !unknown-pinvoke! dispatch_after_f bound !unknown-pinvoke! dispatch_apply_f bound !unknown-pinvoke! dispatch_async_f bound !unknown-pinvoke! dispatch_barrier_async_f bound +!unknown-pinvoke! dispatch_barrier_sync_f bound !unknown-pinvoke! dispatch_data_create bound !unknown-pinvoke! dispatch_data_create_concat bound !unknown-pinvoke! dispatch_data_create_map bound @@ -949,8 +951,15 @@ !unknown-pinvoke! dispatch_group_leave bound !unknown-pinvoke! dispatch_group_notify_f bound !unknown-pinvoke! dispatch_group_wait bound +!unknown-pinvoke! dispatch_queue_attr_make_initially_inactive bound +!unknown-pinvoke! dispatch_queue_attr_make_with_autorelease_frequency bound +!unknown-pinvoke! dispatch_queue_attr_make_with_qos_class bound !unknown-pinvoke! dispatch_queue_create bound +!unknown-pinvoke! dispatch_queue_create_with_target bound !unknown-pinvoke! dispatch_queue_get_label bound +!unknown-pinvoke! dispatch_queue_get_qos_class bound +!unknown-pinvoke! dispatch_queue_get_specific bound +!unknown-pinvoke! dispatch_queue_set_specific bound !unknown-pinvoke! dispatch_read bound !unknown-pinvoke! dispatch_release bound !unknown-pinvoke! dispatch_resume bound