diff --git a/README.ar.md b/README.ar.md index be9362a..8d706fc 100644 --- a/README.ar.md +++ b/README.ar.md @@ -1,4 +1,5 @@ # تـوازي (Threading) + [[English]](README.md) مكتبة لإنشاء المسالك (threads) وتمكين المزامنة (synchronization) بينها في لغة الأسس. @@ -7,7 +8,7 @@ أضف المكتبة لمشروعك باستخدام مدير الحزم: -
+
``` اشمل "مـحا"؛ @@ -21,12 +22,13 @@ import "Apm"; Apm.importPackage("Alusus/Threading@0.1"); ``` +
## الدالات ### أنشئ_مسلكا (createThread) -
+
``` دالة أنشئ_مسلكا( @@ -47,7 +49,8 @@ Apm.importPackage("Alusus/Threading@0.1"); arg: ptr ): Int; ``` -دالة لإنشاء مسلك. تطابق هذه الدالة دالة `pthread_create` من Posix. + +دالة لإنشاء مسلك. تطابق هذه الدالة دالة `pthread_create` من Posix. المعطيات: @@ -65,7 +68,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### التق_بمسلك (joinThread) -
+
``` دالة التق_بمسلك(مسلك: مؤشر[مـسلك]، نتيجة: مؤشر[مؤشر]): صـحيح؛ @@ -76,6 +79,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func joinThread(pthread: ptr[Thread], retval: ptr[ptr]): Int; ``` + تنتظر مسلكا حتى يكتمل تنفيذه وتستلم منه القيمة المرجعة من دالة المسلك. هذه الدالة تطابق دالة `pthread_join` من Posix. المعطيات: @@ -90,7 +94,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### هيئ_مزامنا (initMutex) -
+
``` دالة هيئ_مزامنا(مزامن: مؤشر[مـزامن]، مزايا: مؤشر[مـزايا_مزامن]): صـحيح؛ @@ -101,6 +105,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func initMutex(mutex: ptr[Mutex], attrs: ptr[MutexAttributes]): Int; ``` + تهيئ مزامنا. يجب تهيئة المزامن باستخدام هذه الدالة قبل التمكن من استخدامه. هذه الدالة تطابق دالة `pthread_mutex_init` من Posix. @@ -116,7 +121,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### اقفل_مزامنا (lockMutex) -
+
``` دالة اقفل_مزامنا(مزامن: مؤشر[مـزامن]): صـحيح؛ @@ -127,6 +132,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func lockMutex(mutex: ptr[Mutex]): Int; ``` + تقفل المزامن وتحجزه للمسلك الحالي. إن كان المزامن محجوزا من قبل مسلك آخر فسيُجمد هذا المسلك حتى يتم تحرير المزامن من قبل المسلك الآخر. هذه الدالة تطابق دالة `pthread_mutex_lock` من Posix. @@ -140,7 +146,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### حاول_قفل_مزامن (tryLockMutex) -
+
``` دالة حاول_قفل_مزامن(مزامن: مؤشر[مـزامن]): صـحيح؛ @@ -151,6 +157,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func tryLockMutex(mutex: ptr[Mutex]): Int; ``` + تقفل المزامن إذا كان حرًا، وتُرجع رمز خطأ مباشرة دون انتظار إذا كان المزامن محجوزًا من قبل مسلك آخر. هذه الدالة تطابق دالة `pthread_mutex_trylock` من Posix. @@ -164,7 +171,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### افتح_مزامنا (unlockMutex) -
+
``` دالة افتح_مزامنا(مزامن: مؤشر[مـزامن]): صـحيح؛ @@ -175,6 +182,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func unlockMutex(mutex: ptr[Mutex]): Int; ``` + تحرر مزامنًا وتجعله متوفرًا للمسالك الأخرى. هذه الدالة تطابق دالة `pthread_mutex_unlock` من Posix. المعطيات: @@ -187,7 +195,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### هيئ_شرطا (initCond) -
+
``` دالة هيئ_شرطا(شرط: مؤشر[شـرط]، مزايا: مؤشر[مـزايا_شرط]): صـحيح؛ @@ -198,12 +206,13 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func initCond(cond: ptr[Cond], attrs: ptr[CondAttributes]): Int; ``` + يهيئ كائنا من صنف `شـرط`. يجب تهيئة الشرط بهذه الدالة قبل استخدامه. هذه الدالة تطابق دالة `pthread_cond_init` من Posix. المعطيات: -`شرد`: مؤشر إلى الشرط المراد تهيئته. +`شرط`: مؤشر إلى الشرط المراد تهيئته. `مزايا`: المزايا المطلوبة للشرط. تمرير 0 يؤدي إلى تهيئته بالمزايا الافتراضية. @@ -213,7 +222,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### علم_شرطا (signalCond) -
+
``` دالة علم_شرطا(شرط: مؤشر[شـرط]): صـحيح؛ @@ -224,6 +233,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func signalCond(cond: ptr[Cond]): Int; ``` + يُرسل إشارة بأن الشرط المعني قد تحقق. يؤدي ذلك لتحرير مسلك واحد من المسالك المنتظرة لهذا الشرط. إن وُجد عدة مسالك تنتظر الشرط فسيُحرر واحد منها فقط لكل استدعاء لهذه الدالة. قبل استدعاء هذه الدالة يجب على المسلك قفل مزامن (نفس المزامن المستخدم من قبل المسالك التي تستدعي `انتظر_شرطا` `waitCond`) ويجب تحرير @@ -241,7 +251,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ### انتظر_شرطا (waitCond) -
+
``` دالة انتظر_شرطا(شرط: مؤشر[شـرط]، مزامن: مؤشر[مـزامن]): صـحيح؛ @@ -252,6 +262,7 @@ Apm.importPackage("Alusus/Threading@0.1"); ``` func waitCond(cond: ptr[Cond], mutex: ptr[Mutex]): Int; ``` + تنتظر تحقق شرط. تستقبل هذه الدالة مزامنا بالإضافة إلى الشرط المعني وتتطلب أن يكون المزامن مقفلًا قبل استداءها. تعمل الدالة بشكل لا انشطاري (atomic) على تجميد المسلك وتحرير المزامن. عند تحقق الشرط تقوم الدالة بشكل لا انشطاري بقفل المزامن مجددًا وتحرير المسلك. @@ -267,7 +278,6 @@ Apm.importPackage("Alusus/Threading@0.1"); تُرجع الدالة 0 في حالة نجاح العملية، ورمز خطئ في حالة فشلها. - ## الأصناف ### مـزايا_مسلك (ThreadAttributes) @@ -306,6 +316,146 @@ class ThreadAttributes { } ``` +#### أعلام (flags) + +
+ +``` +عرف أعلام: صـحيح؛ +``` + +
+ +``` +def flags: Int; +``` + +#### حجم_المكدس (stackSize) + +
+ +``` +عرف حجم_المكدس: صـحيح؛ +``` + +
+ +``` +def stackSize: Int; +``` + +#### مجال_التنافس (contentionScope) + +
+ +``` +عرف مجال_التنافس: صـحيح؛ +``` + +
+ +``` +def contentionScope: Int; +``` + +#### توريث_الجدولة (inheritSched) + +
+ +``` +عرف توريث_الجدولة: صـحيح؛ +``` + +
+ +``` +def inheritSched: Int; +``` + +#### فصل_الحالة (detachState) + +
+ +``` +عرف فصل_الحالة: صـحيح؛ +``` + +
+ +``` +def detachState: Int; +``` + +#### جدولة (sched) + +
+ +``` +عرف جدولة: صـحيح؛ +``` + +
+ +``` +def sched: Int; +``` + +#### معلمة (param) + +
+ +``` +عرف معلمة: مـعامل_جدولة؛ +``` + +
+ +``` +def param: SchedParam; +``` + +#### وقت_البداية (startTime) + +
+ +``` +عرف وقت_البداية: تـفصيل_زمني؛ +``` + +
+ +``` +def startTime: TimeSpec; +``` + +#### حد_اقصى (deadLine) + +
+ +``` +عرف حد_اقصى: تـفصيل_زمني؛ +``` + +
+ +``` +def deadLine: TimeSpec; +``` + +#### فترة (period) + +
+ +``` +عرف فترة: تـفصيل_زمني؛ +``` + +
+ +``` +def period: TimeSpec; +``` + ### مـعامل_جدولة (SchedParam)
@@ -313,7 +463,7 @@ class ThreadAttributes { ``` صنف مـعامل_جدولة { عرف أولوية_الجدولة: صـحيح؛ -}؛ +} ```
@@ -324,6 +474,20 @@ class SchedParam { } ``` +#### أولوية_الجدولة (schedPriority) + +
+ +``` +عرف أولوية_الجدولة: صـحيح؛ +``` + +
+ +``` +def schedPriority: Int; +``` + ### تـفصيل_زمني (TimeSpec)
@@ -344,12 +508,61 @@ class TimeSpec { } ``` +#### مدة_ثواني (tvSec) + +
+ +``` +عرف مدة_ثواني: صـحيح_متكيف؛ +``` + +
+ +``` +def tvSec: ArchInt; +``` + +#### مدة_نانو (tvNsec) + +
+ +``` +عرف مدة_نانو: صـحيح[64]؛ +``` +
+ +``` +def tvNsec: Int[64]; +``` + ### مـزامن (Mutex) +
+ +``` +صنف مـزامن { + عملية هذا.هيئ(); + عملية هذا.هيئ(مزايا : سند[مـزايا_مزامن]); + عملية هذا.اقفل(); + عملية هذا.افتح(); +} +``` + +
+ +``` +class Mutex { + handler this.init(); + handler this.init(attrs: ref[MutexAttributes]); + handler this.lock(); + handler this.unlock() +} +``` + يستخدم للمزامنة بين المسالك المختلفة ويتم ذلك بطلب المسلك قفل المزامن، ومن ثم بعد الحصول على القفل يقوم بالعمل المراد ثم يحرر القفل ليتسنى لمسلك آخر قفله. -* `هيئ`: دالة لتهيئة المزامن. يجب استدعاء هذه الدالة قبل استخدام المزامن. لها صيغتان: +#### هيئ (init)
@@ -361,14 +574,43 @@ class TimeSpec {
``` -handler this.init(); -handler this.init(attr: ref[MutexAttributes]); +handler this.init() +handler this.init(attr: ref[MutexAttributes]) +``` + +دالة لتهيئة المزامن. يجب استدعاء هذه الدالة قبل استخدام المزامن. + +#### اقفل (lock) + +
+ +``` +عملية هذا.اقفل(); +``` + +
+ +``` +handler this.lock(); +``` + +تستخدم لقفل المزامن ولا تستلم أي معطيات. عند استدعاء هذه الدالة يُلبث المسلك لحين تحرر هذا المزامن من أي قفل. + +#### افتح (unlock) + +
+ +``` +عملية هذا.افتح(); ``` -* `اقفل`: تستخدم لقفل المزامن ولا تستلم أي معطيات. عند استدعاء هذه الدالة يُلبث المسلك لحين تحرر هذا - المزامن من أي قفل. +
+ +``` +handler this.unlock(); +``` -* `افتح`: تفتح القفل ما يتيح لنظام التشغيل تحرير أحد المسالك المُنتظِرة لهذا القفل. +تفتح القفل ما يتيح لنظام التشغيل تحرير أحد المسالك المُنتظِرة لهذا القفل. ### مـزايا_مزامن (MutexAttributes) @@ -381,6 +623,30 @@ class MutexAttributes { } ``` +#### pshared + +``` +def pshared: Int; +``` + +#### kind + +``` +def kind: Int; +``` + +#### protocol + +``` +def protocol: Int; +``` + +#### robustness + +``` +def robustness: Int; +``` + ### مـزايا_شرط (CondAttributes) ``` @@ -389,6 +655,12 @@ class CondAttributes { } ``` +#### dummy + +``` +def dummy: Int; +``` + ### مـحلي_لمسلك (ThreadLocal) قالب أصناف يستخدم لإنشاء متغيرات محلية نسبة إلى مسلك، أي عمومية ضمن مسلك معين لكنها لا تُشارك مع @@ -430,10 +702,9 @@ Console.print(var.value.i); في هذا المثال نعرف `متغير` (`var`) ليحتوي على قيمة من صنف `صـنفي` (`MyType`) وعند إنشاء المتغير ذو الصنف `صـنفي` ضمن مسلك معين تُحدد قيمة الخصلة `ع` (`i`) منه بقيمة عشوائية. - ## مثال -
+
``` اشمل "مـتم/طـرفية"؛ @@ -552,9 +823,6 @@ func calculateSum(p: ptr): ptr { totalSum(); ``` ---- - ## الرخصة هذا المشروع مرخص بموجب رخصة غنو العمومية الصغرى الإصدار 3.0 (LGPL-3.0). راجع ملفات `COPYING` و `COPYING.LESSER` للحصول على التفاصيل. - diff --git a/README.md b/README.md index ed31c64..dde8975 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Threading + [[عربي]](README.ar.md) Provides threading and thread synchronization functionality. @@ -12,159 +13,110 @@ import "Apm"; Apm.importPackage("Alusus/Threading@0.1"); ``` - ## Functions ### createThread + ``` func createThread( - pthread: ptr[Thread], attr: ptr[ThreadAttributes], startRoutine: ptr[func (ptr): ptr], arg: ptr + pthread: ptr[Thread], + attr: ptr[ThreadAttributes], + startRoutine: ptr[func (ptr): ptr], + arg: ptr ): Int; ``` -A function to create a thread. This function is equivalent to Posix `pthread_create`. -Arguments: +Create a thread. This function is equivalent to Posix `pthread_create`. Returns 0 on success, an error code otherwise. * `pthread`: A pointer to a variable of type Thread, in which the result is stored. * `attr`: The attributes we want for the thread. Passing null indicates default attributes. * `startRoutine`: A pointer to a function that will be executed on this thread. -* `arg`: An arbitrary pointer to pass to the function in the preceding argument. This is used to pass user data to the - thread. - -Return Value: - -Returns 0 on success, an error code otherwise. +* `arg`: An arbitrary pointer to pass to the function in the preceding argument. This is used to pass user data to the thread. ### joinThread + ``` - func joinThread(pthread: ptr[Thread], retval: ptr[ptr]): Int; +func joinThread(pthread: ptr[Thread], retval: ptr[ptr]): Int ``` -Waits for a thread to finish execution. This function is equivalent to Posix `pthread_join`. -Arguments: +Waits for a thread to finish execution. This function is equivalent to Posix `pthread_join`. Returns 0 on success, an error code otherwise. * `pthread`: A pointer to the thread to wait for. -* `retval`: A pointer to store the result of executing the given thread's function. This will contain the value - returned by the thread function. - -Return Value: - -Returns 0 on success, an error code otherwise. +* `retval`: A pointer to store the result of executing the given thread's function. This will contain the value returned by the thread function. ### initMutex + ``` - func initMutex(mutex: ptr[Mutex], attrs: ptr[MutexAttributes]): Int; +func initMutex(mutex: ptr[Mutex], attrs: ptr[MutexAttributes]): Int ``` -Initializes a mutex lock. A mutex must be initialized by this function before it can be used. This function is -equivalent to Posix `pthread_mutex_init`. -Arguments: +Initializes a mutex lock. A mutex must be initialized by this function before it can be used. This function is equivalent to Posix `pthread_mutex_init`. Returns 0 on success, an error code otherwise. * `mutex`: A pointer to the Mutex object. * `attrs`: Attributes for this mutex. Passing null indicates default attributes. -Return Value: - -Returns 0 on success, an error code otherwise. - ### lockMutex + ``` - func lockMutex(mutex: ptr[Mutex]): Int; +func lockMutex(mutex: ptr[Mutex]): Int ``` -Locks the mutex object. If the mutex is locked by another thread the current thread will be paused until the mutex is -available. This function is equivalent to Posix `pthread_mutex_lock`. -Arguments: +Locks the mutex object. If the mutex is locked by another thread the current thread will be paused until the mutex is available. This function is equivalent to Posix `pthread_mutex_lock`. Returns 0 on success, an error code otherwise. * `mutex`: A pointer to the Mutex object. -Return Value: - -Returns 0 on success, an error code otherwise. - ### tryLockMutex + ``` - func tryLockMutex(mutex: ptr[Mutex]): Int; +func tryLockMutex(mutex: ptr[Mutex]): Int ``` -Locks the mutex object. If the mutex is locked by another thread the function returns immediately with an error code. -This function is equivalent to Posix `pthread_mutex_trylock`. -Arguments: +Locks the mutex object. If the mutex is locked by another thread the function returns immediately with an error code. This function is equivalent to Posix `pthread_mutex_trylock`. Returns 0 on success, an error code otherwise. * `mutex`: A pointer to the Mutex object. -Return Value - -Returns 0 on success, an error code otherwise. - ### unlockMutex + ``` - func unlockMutex(mutex: ptr[Mutex]): Int; +func unlockMutex(mutex: ptr[Mutex]): Int ``` -Unlocks a mutex. This function is equivalent to Posix `pthread_mutex_unlock`. -Arguments: +Unlocks a mutex. This function is equivalent to Posix `pthread_mutex_unlock`. Returns 0 on success, an error code otherwise. * `mutex`: A pointer to the Mutex object. -Return Value - -Returns 0 on success, an error code otherwise. - ### initCond + ``` - func initCond(cond: ptr[Cond], attrs: ptr[CondAttributes]): Int; +func initCond(cond: ptr[Cond], attrs: ptr[CondAttributes]): Int ``` -Initializes a condition object. A condition must be initialized by this function before it can be used. -This function is equivalent to Posix `pthread_cond_init`. -Arguments: +Initializes a condition object. A condition must be initialized by this function before it can be used. This function is equivalent to Posix `pthread_cond_init`. Returns 0 on success, an error code otherwise. * `cond`: A pointer to the Cond object. * `attrs`: Attributes for this condition. Passing null indicates default attributes. -Return Value: - -Returns 0 on success, an error code otherwise. - ### signalCond + ``` - func signalCond(cond: ptr[Cond]): Int; +func signalCond(cond: ptr[Cond]): Int ``` -Sends a signal that a condition is met. This will unlock one thread that is waiting on this condition. If multiple -threads are waiting for the same condition only one of them will be released per call. Before calling this function -the calling thread must first lock a mutex (the same mutex used by the thread in `waitCond`) and must release the -mutex after the call. The thread calling `waitCond` will only be released after the mutex is released since the -`waitCond` will attempt to lock the mutex before returning. -This is equivalent to Posix `pthread_cond_signal`. -Arguments: +Sends a signal that a condition is met. This will unlock one thread that is waiting on this condition. If multiple threads are waiting for the same condition only one of them will be released per call. Before calling this function the calling thread must first lock a mutex (the same mutex used by the thread in `waitCond`) and must release the mutex after the call. The thread calling `waitCond` will only be released after the mutex is released since the `waitCond` will attempt to lock the mutex before returning. This is equivalent to Posix `pthread_cond_signal`. Returns 0 on success, an error code otherwise. * `cond`: A pointer to the condition object to signal. -Return Value: - -Returns 0 on success, an error code otherwise. - ### waitCond + ``` - func waitCond(cond: ptr[Cond], mutex: ptr[Mutex]): Int; +func waitCond(cond: ptr[Cond], mutex: ptr[Mutex]): Int ``` -Waits for a condition to be met. This takes a mutex in addition to the condition to wait for, and it requires that the -mutex is locked before the call. The function will atomically block the thread and release the mutex. When the condition -is signaled the function will atomically re-lock the mutex and release the calling thread. -This function is equivalent to Posix `pthread_cond_wait`. -Arguments: +Waits for a condition to be met. This takes a mutex in addition to the condition to wait for, and it requires that the mutex is locked before the call. The function will atomically block the thread and release the mutex. When the condition is signaled the function will atomically re-lock the mutex and release the calling thread. This function is equivalent to Posix `pthread_cond_wait`. Returns 0 on success, an error code otherwise. * `cond`: A pointer to the Cond object to wait on. * `mutex`: A pointer to a Mutex object. This must be locked before calling this function. -Return Value: - -Returns 0 on success, an error code otherwise. - - ## Types ### ThreadAttributes @@ -184,6 +136,66 @@ class ThreadAttributes { } ``` +#### flags + +``` +def flags: Int; +``` + +#### stackSize + +``` +def stackSize: Int; +``` + +#### contentionScope + +``` +def contentionScope: Int; +``` + +#### inheritSched + +``` +def inheritSched: Int; +``` + +#### detachState + +``` +def detachState: Int; +``` + +#### sched + +``` +def sched: Int; +``` + +#### param + +``` +def param: SchedParam; +``` + +#### startTime + +``` +def startTime: TimeSpec; +``` + +#### deadLine + +``` +def deadLine: TimeSpec; +``` + +#### period + +``` +def period: TimeSpec; +``` + ### SchedParam ``` @@ -192,6 +204,12 @@ class SchedParam { } ``` +#### schedPriority + +``` +def schedPriority: Int; +``` + ### TimeSpec ``` @@ -201,21 +219,56 @@ class TimeSpec { } ``` +#### tvSec + +``` +def tvSec: ArchInt; +``` + +#### tvNsec + +``` +def tvNsec: Int[64]; +``` + ### Mutex +``` +class Mutex { + handler this.init(); + handler this.init(attrs: ref[MutexAttributes]); + handler this.lock(); + handler this.unlock() +} +``` + Used for synchronizing threads by allowing threads to request locking the mutex and release the lock after the thread is done with the synchronized work. -* `init`: Initializes the mutex. This must be called before the mutex is usable. It has two forms: +#### init ``` -handler this.init(); -handler this.init(attr: ref[MutexAttributes]); +handler this.init() +handler this.init(attr: ref[MutexAttributes]) ``` -* `lock`: Locks the mutex. This will freeze the thread until the lock is available. +Initializes the mutex. This must be called before the mutex is usable. -* `unlock`: Unlocks the mutex allowing the OS to release another waiting thread. +#### lock + +``` +handler this.lock(); +``` + +Locks the mutex. This will freeze the thread until the lock is available. + +#### unlock + +``` +handler this.unlock(); +``` + +Unlocks the mutex allowing the OS to release another waiting thread. ### MutexAttributes @@ -228,6 +281,30 @@ class MutexAttributes { } ``` +#### pshared + +``` +def pshared: Int; +``` + +#### kind + +``` +def kind: Int; +``` + +#### protocol + +``` +def protocol: Int; +``` + +#### robustness + +``` +def robustness: Int; +``` + ### CondAttributes ``` @@ -236,6 +313,12 @@ class CondAttributes { } ``` +#### dummy + +``` +def dummy: Int; +``` + ### ThreadLocal Type template used to define thread local variables, i.e. variables that are global within a @@ -244,8 +327,7 @@ thread but aren't shared between threads. * `initializer`: A closure used to initialize the variable. It's called automatically when the variable is created within a thread, i.e. it's called for each thread in which the variable is created. If this closure isn't set the variable will only be initialized using the - built-in constructor. This `initializer` can be set by passing it to the constructor - of `ThreadLocal` or by setting the `initializer` member value directly. + built-in constructor. This `initializer` can be set by passing it to the constructor of `ThreadLocal` or by setting the `initializer` member value directly. * `value`: Used to access the actual value. Accessing this attribute for the first time within a thread will result in allocating and initializing the variable before returning a reference @@ -265,7 +347,6 @@ Console.print(var.value.i); In this example `var` is declared to contain a value of type `MyType`. When the variable of type `MyType` is created within a thread its `i` attribute will be set to a random value. - ## Example ``` @@ -326,9 +407,6 @@ func calculateSum(p: ptr): ptr { totalSum(); ``` ---- - ## License -This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0). See the `COPYING` and `COPYING.LESSER` files for details. - +This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0). See the `COPYING` and `COPYING.LESSER` files for details. \ No newline at end of file