• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-dotnet / 5716220196

31 Jul 2023 02:52PM UTC coverage: 82.478% (-0.3%) from 82.741%
5716220196

Pull #3261

github

aed2eb
fealebenpae
Merge remote-tracking branch 'origin/main' into yg/updated-marshaling

# Conflicts:
#	Realm/Realm/Handles/SharedRealmHandle.cs
Pull Request #3261: Use modern-er marshaling techniques

2029 of 2601 branches covered (78.01%)

Branch coverage included in aggregate %.

201 of 201 new or added lines in 21 files covered. (100.0%)

6246 of 7432 relevant lines covered (84.04%)

34048.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.41
/Realm/Realm/Handles/SubscriptionSetHandle.cs
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2021 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
using System;
20
using System.Runtime.InteropServices;
21
using System.Threading.Tasks;
22
using MongoDB.Bson;
23
using Realms.Native;
24
using Realms.Sync.Exceptions;
25

26
namespace Realms.Sync
27
{
28
    internal class SubscriptionSetHandle : RealmHandle
29
    {
30
#pragma warning disable IDE0049 // Use built-in type alias
31
#pragma warning disable SA1121 // Use built-in type alias
32

33
        private static class NativeMethods
34
        {
35
            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
36
            public delegate void StateWaitCallback(IntPtr task_completion_source, SubscriptionSetState new_state, PrimitiveValue message);
37

38
            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
39
            public delegate void GetSubscriptionCallback(IntPtr managed_callback, Native.Subscription subscription);
40

41
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_install_callbacks", CallingConvention = CallingConvention.Cdecl)]
42
            public static extern void install_callbacks(
43
                GetSubscriptionCallback get_subscription_callback,
44
                StateWaitCallback state_wait_callback);
45

46
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_count", CallingConvention = CallingConvention.Cdecl)]
47
            public static extern IntPtr get_count(SubscriptionSetHandle handle, out NativeException ex);
48

49
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_version", CallingConvention = CallingConvention.Cdecl)]
50
            public static extern Int64 get_version(SubscriptionSetHandle handle, out NativeException ex);
51

52
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_state", CallingConvention = CallingConvention.Cdecl)]
53
            public static extern SubscriptionSetState get_state(SubscriptionSetHandle handle, out NativeException ex);
54

55
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_at_index", CallingConvention = CallingConvention.Cdecl)]
56
            public static extern void get_at_index(SubscriptionSetHandle handle, IntPtr index, IntPtr callback, out NativeException ex);
57

58
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_find_by_name", CallingConvention = CallingConvention.Cdecl)]
59
            public static extern void find_by_name(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr name_len, IntPtr callback, out NativeException ex);
60

61
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_find_by_query", CallingConvention = CallingConvention.Cdecl)]
62
            public static extern void find_by_query(SubscriptionSetHandle handle, ResultsHandle results, IntPtr callback, out NativeException ex);
63

64
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_add_results", CallingConvention = CallingConvention.Cdecl)]
65
            public static extern void add(SubscriptionSetHandle handle, ResultsHandle results,
66
                [MarshalAs(UnmanagedType.LPWStr)] string? name, IntPtr name_len,
67
                [MarshalAs(UnmanagedType.I1)] bool update_existing, IntPtr callback, out NativeException ex);
68

69
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove", CallingConvention = CallingConvention.Cdecl)]
70
            [return: MarshalAs(UnmanagedType.I1)]
71
            public static extern bool remove(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr name_len, out NativeException ex);
72

73
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_by_id", CallingConvention = CallingConvention.Cdecl)]
74
            [return: MarshalAs(UnmanagedType.I1)]
75
            public static extern bool remove(SubscriptionSetHandle handle, PrimitiveValue id, out NativeException ex);
76

77
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_by_query", CallingConvention = CallingConvention.Cdecl)]
78
            public static extern IntPtr remove(SubscriptionSetHandle handle, ResultsHandle results, [MarshalAs(UnmanagedType.I1)] bool remove_named, out NativeException ex);
79

80
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_by_type", CallingConvention = CallingConvention.Cdecl)]
81
            public static extern IntPtr remove_by_type(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.LPWStr)] string type, IntPtr type_len, [MarshalAs(UnmanagedType.I1)] bool remove_named, out NativeException ex);
82

83
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_remove_all", CallingConvention = CallingConvention.Cdecl)]
84
            public static extern IntPtr remove_all(SubscriptionSetHandle handle, [MarshalAs(UnmanagedType.I1)] bool remove_named, out NativeException ex);
85

86
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_destroy", CallingConvention = CallingConvention.Cdecl)]
87
            public static extern void destroy(IntPtr handle);
88

89
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_destroy_mutable", CallingConvention = CallingConvention.Cdecl)]
90
            public static extern void destroy_mutable(IntPtr handle);
91

92
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_wait_for_state", CallingConvention = CallingConvention.Cdecl)]
93
            public static extern void wait_for_state(SubscriptionSetHandle handle, IntPtr task_completion_source, out NativeException ex);
94

95
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_begin_write", CallingConvention = CallingConvention.Cdecl)]
96
            public static extern IntPtr begin_write(SubscriptionSetHandle handle, out NativeException ex);
97

98
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_commit_write", CallingConvention = CallingConvention.Cdecl)]
99
            public static extern IntPtr commit_write(SubscriptionSetHandle handle, out NativeException ex);
100

101
            [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_subscriptionset_get_error", CallingConvention = CallingConvention.Cdecl)]
102
            public static extern IntPtr get_error_message(SubscriptionSetHandle handle, IntPtr buffer, IntPtr buffer_length, [MarshalAs(UnmanagedType.U1)] out bool isNull, out NativeException ex);
103
        }
104

105
#pragma warning restore IDE0049 // Use built-in type alias
106
#pragma warning restore SA1121 // Use built-in type alias
107

108
        private delegate void GetSubscriptionBase(IntPtr callback, out NativeException ex);
109

110
        public override bool ForceRootOwnership => true;
382✔
111

112
        public static void Initialize()
113
        {
114
            NativeMethods.GetSubscriptionCallback getSubscription = OnGetSubscription;
1✔
115
            NativeMethods.StateWaitCallback waitState = HandleStateWaitCallback;
1✔
116

117
            GCHandle.Alloc(getSubscription);
1✔
118
            GCHandle.Alloc(waitState);
1✔
119

120
            NativeMethods.install_callbacks(getSubscription, waitState);
1✔
121
        }
1✔
122

123
        public bool IsReadonly { get; }
704✔
124

125
        public SubscriptionSetHandle(SharedRealmHandle root, IntPtr handle, bool isReadonly = true) : base(root, handle)
382✔
126
        {
127
            IsReadonly = isReadonly;
382✔
128
        }
382✔
129

130
        public int GetCount()
131
        {
132
            EnsureIsOpen();
118✔
133

134
            var result = NativeMethods.get_count(this, out var ex);
115✔
135
            ex.ThrowIfNecessary();
115✔
136
            return (int)result;
115✔
137
        }
138

139
        public SubscriptionSetState GetState()
140
        {
141
            EnsureIsOpen();
33✔
142

143
            var state = NativeMethods.get_state(this, out var ex);
33✔
144
            ex.ThrowIfNecessary();
33✔
145
            return state;
33✔
146
        }
147

148
        public long GetVersion()
149
        {
150
            EnsureIsOpen();
489✔
151

152
            var result = NativeMethods.get_version(this, out var ex);
489✔
153
            ex.ThrowIfNecessary();
489✔
154
            return result;
489✔
155
        }
156

157
        public string? GetErrorMessage()
158
        {
159
            EnsureIsOpen();
16✔
160

161
            return MarshalHelpers.GetString((IntPtr buffer, IntPtr length, out bool isNull, out NativeException ex) =>
16✔
162
                NativeMethods.get_error_message(this, buffer, length, out isNull, out ex));
32✔
163
        }
164

165
        public SubscriptionSetHandle BeginWrite()
166
        {
167
            EnsureIsOpen();
132✔
168

169
            var result = NativeMethods.begin_write(this, out var ex);
132✔
170
            ex.ThrowIfNecessary();
132✔
171
            return new SubscriptionSetHandle(Root!, result, isReadonly: false);
132✔
172
        }
173

174
        public SubscriptionSetHandle CommitWrite()
175
        {
176
            EnsureIsOpen();
128✔
177

178
            var result = NativeMethods.commit_write(this, out var ex);
128✔
179
            ex.ThrowIfNecessary();
128✔
180

181
            return new SubscriptionSetHandle(Root!, result, isReadonly: true);
128✔
182
        }
183

184
        public Subscription GetAtIndex(int index)
185
        {
186
            EnsureIsOpen();
48✔
187

188
            return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.get_at_index(this, (IntPtr)index, callback, out ex));
96✔
189
        }
190

191
        public Subscription Find(string name)
192
        {
193
            EnsureIsOpen();
5✔
194

195
            return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.find_by_name(this, name, name.IntPtrLength(), callback, out ex));
10✔
196
        }
197

198
        public Subscription Find(ResultsHandle results)
199
        {
200
            EnsureIsOpen();
8✔
201

202
            return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.find_by_query(this, results, callback, out ex));
16✔
203
        }
204

205
        public Subscription Add(ResultsHandle results, SubscriptionOptions options)
206
        {
207
            EnsureIsOpen();
157✔
208

209
            return GetSubscriptionCore((IntPtr callback, out NativeException ex) => NativeMethods.add(this, results, options.Name, options.Name.IntPtrLength(), options.UpdateExisting, callback, out ex));
314✔
210
        }
211

212
        public bool Remove(string name)
213
        {
214
            EnsureIsOpen();
5✔
215

216
            var result = NativeMethods.remove(this, name, name.IntPtrLength(), out var ex);
5✔
217
            ex.ThrowIfNecessary();
5✔
218
            return result;
5✔
219
        }
220

221
        public bool Remove(ObjectId id)
222
        {
223
            EnsureIsOpen();
5✔
224

225
            var subId = PrimitiveValue.ObjectId(id);
5✔
226
            var result = NativeMethods.remove(this, subId, out var ex);
5✔
227
            ex.ThrowIfNecessary();
5✔
228
            return result;
5✔
229
        }
230

231
        public int Remove(ResultsHandle results, bool removeNamed)
232
        {
233
            EnsureIsOpen();
8✔
234

235
            var result = NativeMethods.remove(this, results, removeNamed, out var ex);
8✔
236
            ex.ThrowIfNecessary();
8✔
237
            return (int)result;
8✔
238
        }
239

240
        public int RemoveAll(string type, bool removeNamed)
241
        {
242
            EnsureIsOpen();
4✔
243

244
            var result = NativeMethods.remove_by_type(this, type, type.IntPtrLength(), removeNamed, out var ex);
4✔
245
            ex.ThrowIfNecessary();
4✔
246
            return (int)result;
4✔
247
        }
248

249
        public int RemoveAll(bool removeNamed)
250
        {
251
            EnsureIsOpen();
3✔
252

253
            var result = NativeMethods.remove_all(this, removeNamed, out var ex);
3✔
254
            ex.ThrowIfNecessary();
3✔
255
            return (int)result;
3✔
256
        }
257

258
        public async Task<SubscriptionSetState> WaitForStateChangeAsync()
259
        {
260
            EnsureIsOpen();
55✔
261

262
            var tcs = new TaskCompletionSource<SubscriptionSetState>();
55✔
263
            var tcsHandle = GCHandle.Alloc(tcs);
55✔
264

265
            try
266
            {
267
                NativeMethods.wait_for_state(this, GCHandle.ToIntPtr(tcsHandle), out var ex);
55✔
268
                ex.ThrowIfNecessary();
55✔
269

270
                return await tcs.Task;
55✔
271
            }
272
            catch (Exception ex) when (ex.Message == "Active SubscriptionSet without a SubscriptionStore")
2✔
273
            {
274
                throw new TaskCanceledException("The SubscriptionSet was closed before the wait could complete. This is likely because the Realm it belongs to was disposed.");
1✔
275
            }
276
            finally
277
            {
278
                tcsHandle.Free();
55✔
279
            }
280
        }
53✔
281

282
        private static Subscription GetSubscriptionCore(GetSubscriptionBase getter)
283
        {
284
            Subscription? result = null;
218✔
285
            Action<Native.Subscription> callback = sub => result = sub.ManagedSubscription;
433✔
286
            var callbackHandle = GCHandle.Alloc(callback);
218✔
287
            try
288
            {
289
                getter(GCHandle.ToIntPtr(callbackHandle), out var ex);
218✔
290
                ex.ThrowIfNecessary();
218✔
291
            }
215✔
292
            finally
293
            {
294
                callbackHandle.Free();
218✔
295
            }
218✔
296

297
            return result!;
215✔
298
        }
299

300
        public override void Unbind()
301
        {
302
            if (IsReadonly)
382✔
303
            {
304
                NativeMethods.destroy(handle);
250✔
305
            }
306
            else
307
            {
308
                NativeMethods.destroy_mutable(handle);
132✔
309
            }
310

311
            handle = IntPtr.Zero;
382✔
312
        }
382✔
313

314
        [MonoPInvokeCallback(typeof(NativeMethods.GetSubscriptionCallback))]
315
        private static void OnGetSubscription(IntPtr managedCallbackPtr, Native.Subscription subscription)
316
        {
317
            var handle = GCHandle.FromIntPtr(managedCallbackPtr);
215✔
318
            var callback = (Action<Native.Subscription>)handle.Target!;
215✔
319
            callback(subscription);
215✔
320
        }
215✔
321

322
        [MonoPInvokeCallback(typeof(NativeMethods.StateWaitCallback))]
323
        private static void HandleStateWaitCallback(IntPtr taskCompletionSource, SubscriptionSetState state, PrimitiveValue message)
324
        {
325
            var handle = GCHandle.FromIntPtr(taskCompletionSource);
55✔
326
            var tcs = (TaskCompletionSource<SubscriptionSetState>)handle.Target!;
55✔
327

328
            switch (message.Type)
55!
329
            {
330
                case RealmValueType.Null:
331
                    tcs.TrySetResult(state);
53✔
332
                    break;
53✔
333
                case RealmValueType.Int when message.AsInt() == -1:
×
334
                    tcs.TrySetException(new TaskCanceledException("The SubscriptionSet was closed before the wait could complete. This is likely because the Realm it belongs to was disposed."));
×
335
                    break;
×
336
                default:
337
                    tcs.TrySetException(new SubscriptionException(message.AsString()));
2✔
338
                    break;
339
            }
340
        }
2✔
341
    }
342
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc