هل من السهل إضافة ميزات جديدة إلى الإطار القديم؟ طحين الاختيار على سبيل المثال في تطوير SObjectizer



يعد تطوير إطار عمل مجاني لاحتياجات المطورين موضوعًا محددًا. إذا كان الإطار في الوقت نفسه يعيش ويتطور لفترة طويلة إلى حد ما ، فسيتم إضافة التفاصيل. اليوم سأحاول إظهار ذلك باستخدام مثال لمحاولة توسيع وظائف إطار عمل "فاعل" لـ C ++ يسمى SObjectizer .

والحقيقة هي أن هذا الإطار قديم بالفعل ، وقد تغير بشكل كبير عدة مرات. حتى تجسده الحالي ، SObjectizer-5 ، خضع للعديد من التغييرات ، سواء كانت خطيرة أو غير ذلك. بالإضافة إلى ذلك ، نحن حساسون إلى حد ما للتوافق ، وإجراء التغييرات التي تكسر التوافق أمر بالغ الخطورة بالنسبة لنا لاتخاذ قرار بشأنه.

نحتاج الآن إلى تحديد كيفية إضافة ميزة جديدة إلى الإصدار التالي. في عملية إيجاد حل مناسب ، ظهر خياران. كلاهما يبدو قابلاً للتحقيق تمامًا. لكنهم مختلفون جدا عن بعضهم البعض. سواء من حيث التعقيد أو التعقيد في التنفيذ و "مظهره". على سبيل المثال ما سيتعامل معه المطور سيبدو مختلفًا في كل خيار. ربما تختلف اختلافا جوهريا.

والآن ، بصفتنا مطورين للإطار ، علينا أن نختار خيارًا لصالح حل أو آخر. أو يجب على المرء أن يعترف بأن أيا منها ليس مرضيا ، وبالتالي هناك حاجة إلى اختراع شيء آخر. يجب اتخاذ مثل هذه القرارات خلال تاريخ SObjectizer أكثر من مرة. إذا كان شخص ما مهتمًا بالشعور بأحذية مطور مثل هذا الإطار ، فأنت مرحب بك في القط.

المشكلة الأصلية


لذا ، باختصار جوهر المشكلة الأصلية. منذ بداية وجوده ، كان لـ SObjectizer الميزة التالية: رسالة المؤقت ليست سهلة الإلغاء. تحت الموقت سيتم فهم ، أولا وقبل كل شيء ، رسالة متأخرة. على سبيل المثال رسالة لا يجب إرسالها فورًا إلى المستلم ، ولكن بعد مرور بعض الوقت. على سبيل المثال ، تم إرسال send_delayed مع إيقاف مؤقت لـ 1s. هذا يعني أنه في الواقع سيتم إرسال الرسالة بواسطة المؤقت 1 بعد المكالمة المرسلة المتأخرة.

يمكن من حيث المبدأ إلغاء الرسالة المعلقة. إذا كانت الرسالة لا تزال في حوزة المؤقت ، فلن تنتقل الرسالة بعد الإلغاء إلى أي مكان. سيتم طرحه بواسطة المؤقت وهذا كل شيء. ولكن إذا كان المؤقت قد أرسل بالفعل رسالة وهو الآن في قائمة انتظار الطلبات الخاصة بالوكيل المتلقي ، فلن يعمل إلغاء المؤقت. لا توجد آلية في SObjectizer لإزالة رسالة من قائمة انتظار التطبيق.

وتتفاقم المشكلة بعاملين على الأقل.

أولاً ، يدعم SObjectizer التسليم في وضع 1: N ، أي إذا تم إرسال الرسالة إلى mbox متعدد العملاء ، فلن تكون الرسالة في قائمة انتظار واحدة ، ولكن في عدة قوائم انتظار لمستلمين N في وقت واحد.

ثانيًا ، في SObjectizer يتم استخدام آلية المرسل ويمكن أن يكون المرسلون مختلفين تمامًا ، بما في ذلك تلك التي كتبها المستخدم لاحتياجاتهم الخاصة. تتم إدارة قوائم انتظار الطلبات بواسطة المرسلين. وفي واجهة المرسل لا توجد وظيفة لسحب تطبيق تم نقله بالفعل إلى المرسل. ولكن حتى إذا كانت هذه الوظيفة مضمنة في الواجهة ، فليس من الممكن أن يتم تنفيذها بفعالية في جميع الحالات. ناهيك عن حقيقة أن هذه الوظيفة ستزيد من تعقيد تطوير المرسلين الجدد.

بشكل عام ، بشكل موضوعي ، إذا كان المؤقت قد أرسل بالفعل رسالة معلقة إلى المستلم (المستلمين) ، فإن إجبار SObjectizer على عدم تسليم هذا المثيل من الرسالة مستحيل حاليًا.
في الواقع ، هذه المشكلة ذات صلة أيضًا بالرسائل الدورية (أي الرسائل التي يجب أن يرسلها المؤقت بشكل دوري على فترات زمنية محددة مسبقًا). ولكن من الناحية العملية ، يعد إلغاء الرسائل الدورية أقل ضرورة بكثير من إلغاء رسالة معلقة. هذا على الأقل في ممارستنا.

ما الذي يمكن فعله الآن؟


لذا ، فإن هذه المشكلة ليست جديدة ، ولوقت طويل هناك توصيات حول كيفية التعامل معها.

معرف فريد داخل الرسالة المعلقة


أسهل طريقة هي الحفاظ على عداد. لدى الوكيل عداد ؛ عند إرسال رسالة معلقة ، يتم إرسال قيمة العداد الحالية في الرسالة. عند إلغاء الرسالة ، يزداد العداد عند الوكيل. عند استلام الرسالة ، تتم مقارنة قيمة العداد الحالية في الوكيل بالقيمة من الرسالة. إذا لم تتطابق القيم ، فسيتم رفض الرسالة:

class demo_agent : public so_5::agent_t { struct delayed_msg final { int id_; ... }; int expected_msg_id_{}; so_5::timer_id_t timer_; void on_some_event() { //   . //   send_periodic, ..    //  timer_id   . timer_ = so_5::send_periodic<delayed_msg>(*this, 25s, //     . 0s, //    . //      delayed_msg, //      id   . ++expected_msg_id_, ... //  . ); ... } void on_cancel_event() { //   ,        //   .   : timer_.reset(); //     . ++expected_msg_id_; //   id-. ... } void on_delayed_msg(mhood_t<delayed_msg> cmd) { //     id    //  . if(expected_msg_id_ == cmd->id_) { ... //  . } } }; 

المشكلة في هذه الطريقة هي أن المطور الوكيل بحاجة إلى الحيرة من خلال الحفاظ على هذه العدادات. وإذا أردنا كرسالة متأخرة أن نرسل رسالة شخص آخر أرسلها شخص آخر ، ولا يوجد فيها حقل id_ ، فإننا نجد أنفسنا في وضع صعب.

على الرغم من ذلك ، من ناحية أخرى ، هذه هي الطريقة الأكثر فعالية الموجودة حاليًا.

استخدم mbox الفريد للرسالة المتأخرة


هناك طريقة أخرى تعمل بشكل جيد وهي استخدام صندوق بريد فريد (mbox) لرسالة متأخرة. في هذه الحالة ، نقوم بإنشاء mbox جديد لكل رسالة معلقة والاشتراك فيها وإرسال الرسالة المعلقة إلى mbox هذا. عندما يلزم إلغاء رسالة ، فإننا ببساطة نحذف اشتراكات mbox.

 class demo_agent : public so_5::agent_t { struct delayed_msg final { ... //   id_   . }; so_5::mbox_t timer_mbox_; //   . so_5::timer_id_t timer_; void on_some_event() { //        mbox //     . timer_mbox_ = so_environment().create_mbox(); some_state.event(time_mbox_, ...); another_state.event(time_mbox_, ...); ... //    . timer_ = so_5::send_delayed<delayed_msg>( so_environment(), timer_mbox_, //     . 25s, 0s, ... //    delayed_msg. ); } void on_cancel_event() { //        mbox. timer_.reset(); so_drop_subscription_for_all_states(timer_mbox_); } void on_delayed_msg(mhood_t<delayed_msg> cmd) { //     ,   //    . ... } }; 

يمكن أن تعمل هذه الطريقة بالفعل مع رسائل أشخاص آخرين ، لا يوجد داخلها معرف فريد. ولكنه يتطلب أيضًا عملاً واهتمامًا من المطور.

على سبيل المثال ، في النموذج أعلاه ، لا توجد حماية ضد حقيقة أن رسالة معلقة واحدة قد تم إرسالها بالفعل في وقت سابق. بطريقة جيدة ، قبل إرسال رسالة جديدة معلقة ، يجب عليك دائمًا تنفيذ إجراءات من on_cancel_event () ، وإلا فسيكون لدى الوكيل اشتراكات غير ضرورية لها.

لماذا لم يتم حل هذه المشكلة من قبل؟


كل شيء بسيط للغاية هنا: في الواقع ، هذه ليست مشكلة خطيرة كما قد تبدو. على الأقل في الحياة الواقعية ، ليس عليك التعامل معها كثيرًا. عادة لا يتم إلغاء الرسائل المعلقة والدورية على الإطلاق (ولهذا السبب ، بالمناسبة ، لا تقوم وظيفة send_delayed بإرجاع timer_id). وعندما تنشأ الحاجة إلى الإلغاء ، يمكنك استخدام إحدى الطرق الموضحة أعلاه. أو حتى استخدام البعض الآخر. على سبيل المثال ، أنشئ وكلاء مستقلين يعالجون رسالة معلقة. يمكن إلغاء تسجيل هؤلاء الوكلاء عندما يلزم إلغاء رسالة معلقة.

لذلك ، على خلفية المهام الأخرى التي واجهتنا ، لم يكن تبسيط الإلغاء المضمون للرسالة المعلقة أولوية للغاية لإنفاق مواردنا على حل هذه المشكلة.

لماذا هي المشكلة ذات الصلة الآن؟


كل شيء بسيط هنا. من ناحية ، وصلت اليدين أخيرا.

من ناحية أخرى ، عندما يبدأ الأشخاص الجدد الذين ليس لديهم خبرة في العمل معه في استخدام SObjectizer ، فإن هذه الميزة مع إلغاء المؤقتات تفاجئهم كثيرًا. ليس هذا مفاجأة سارة. وإذا كان الأمر كذلك ، فأنا أود تقليل الانطباعات السلبية للتعرف على أداتنا.

بالإضافة إلى ذلك ، كانت لدينا مهامنا الخاصة ، ولم نكن بحاجة إلى إلغاء الرسائل المعلقة باستمرار. ولدى المستخدمين الجدد مهامهم الخاصة ، ربما كل شيء هو العكس.

بيان جديد للمشكلة


على الفور تقريباً ، بمجرد أن بدأ النظر في إمكانية "إلغاء مؤقت مضمون" ، فكرت في ذهني أنه يمكن توسيع المهمة. يمكنك محاولة حل مشكلة استدعاء أي من الرسائل المرسلة سابقًا ، وليس بالضرورة تأخيرها ودوريتها.

من وقت لآخر هذه الفرصة مطلوبة. على سبيل المثال ، تخيل أن لدينا عدة عوامل تفاعل من نوعين: نقطة الدخول (قبول الطلبات من العملاء) والمعالج (طلبات العمليات):



يرسل وكلاء Entry_point الطلبات إلى وكيل المعالج ، الذي يعالجها قدر الإمكان ويستجيب لوكلاء نقطة الدخول. ولكن في بعض الأحيان ، قد تجد entry_point أن معالجة الطلب الذي تم إرساله مسبقًا لم تعد ضرورية. على سبيل المثال ، أرسل العميل أمر إلغاء أو "سقط" العميل ولم تعد بحاجة إلى معالجة طلباته. الآن ، إذا تم وضع رسائل الطلب في قائمة الانتظار بواسطة وكيل المعالج ، فلا يمكنك تذكرها. وسيكون من المفيد.

لذلك ، يتم تنفيذ النهج الحالي لحل مشكلة "إلغاء توقيت مضمون" على وجه التحديد كإضافة دعم "رسائل الاستدعاء". نرسل أي رسالة بطريقة خاصة ، نحصل على مقبض في متناول اليد ، يمكنك بعد ذلك استدعاء الرسالة. وليس من المهم للغاية أن تستجيب رسالة عادية أو متأخرة.

محاولة للتوصل إلى تنفيذ "رسائل الاستدعاء"


لذا ، تحتاج إلى تقديم مفهوم "رسالة الاستدعاء" ودعم هذا المفهوم في SObjectizer. وهكذا ، للبقاء داخل الفرع 5.5. ظهرت النسخة الأولى من هذا الموضوع ، 5.5.0 ، منذ ما يقرب من أربع سنوات ، في أكتوبر 2014. منذ ذلك الحين ، لم تحدث أي تغييرات كبيرة في 5.5. يمكن للمشروعات التي بدلت بالفعل أو بدأت على الفور في SObjectize-5.5 التحول إلى الإصدارات الجديدة في الفرع 5.5 دون أي مشاكل. يجب الحفاظ على هذا التوافق هذه المرة.

بشكل عام ، كل شيء بسيط: عليك أن تأخذ وتفعل.

ما هو واضح كيف نفعل


بعد النهج الأول للمشكلة ، أصبح هناك شيئان واضحان حول تنفيذ "رسائل الاستدعاء".

العلم الذري والتحقق منه قبل معالجة الرسالة


أولاً ، من الواضح أنه في إطار بنية SObjectizer-5.5 الحالية (وربما بشكل أكثر عمومية: في إطار مبادئ SObjectizer-5 نفسها) ، من المستحيل إزالة الرسائل من قوائم انتظار طلب المرسل ، حيث تنتظر الرسائل حتى يقوم وكلاء الاستلام بمعالجتها. ستؤدي محاولة القيام بذلك إلى قتل فكرة المرسلين غير المتجانسين بالكامل ، والتي يمكن حتى للمستخدم القيام بها ، وفقًا لتفاصيل مهمته (على سبيل المثال ، هذه المهمة). بالإضافة إلى ذلك ، في حالة إرسال رسالة في وضع 1: N ، حيث سيكون N كبيرًا ، سيكون من المكلف الاحتفاظ بقائمة من المؤشرات لمثيل الرسالة المرسلة في جميع قوائم الانتظار.

وهذا يعني أنه جنبًا إلى جنب مع الرسالة ، يجب إرسال نوع من العلم الذري ، والذي يجب تحليله فورًا بعد إزالة الرسالة من قائمة انتظار الطلبات ، ولكن قبل إرسال الرسالة للمعالجة إلى وكيل الاستلام. على سبيل المثال تدخل الرسالة قائمة الانتظار ولا تتم إزالتها في أي مكان من هناك. ولكن عندما يأتي دور الرسالة ، يتم التحقق من علمها. وإذا كان العلم يشير إلى أنه تم سحب الرسالة ، فلن تتم معالجة الرسالة.

وبناءً على ذلك ، فإن استدعاء الرسالة نفسه يتكون من تحديد قيمة خاصة للعلم الذري داخل الرسالة.

Revocable_handle_t <M> كائن


ثانيًا ، حتى الآن (؟) من الواضح أنه لإرسال رسالة قابلة للإلغاء ، لا يجب استخدام الطرق المعتادة لإرسال الرسائل ، ولكن يجب استخدام كائن خاص تحت الاسم الشرطي revocable_handle_t.

لإرسال رسالة قابلة للإلغاء ، يجب على المستخدم إنشاء مثيل من revocable_handle_t ، ثم استدعاء طريقة الإرسال في هذا المثيل. وإذا كانت الرسالة بحاجة إلى التذكير ، فسيتم ذلك باستخدام طريقة الإبطال. شيء مثل:

 struct my_message {...}; ... so_5::revocable_handle_t<my_message> msg; //    . msg.send(target, //  . ... //    my_message. ); ... //   . msg.revoke(); 

لا توجد تفاصيل واضحة عن تنفيذ Revocable_handle_t حتى الآن ، وهذا ليس مفاجئًا منذ ذلك الحين لم يتم بعد تحديد آلية عمل رسائل الاستدعاء. لكن مبدأ العمل هو أنه في revocable_handle_t يتم حفظ ارتباط ذكي للرسالة المرسلة وللعلم الذري لها. يحاول أسلوب الإبطال () استبدال قيمة العلم. إذا نجح ذلك ، فلن تتم معالجة الرسالة بعد الاستخراج من قائمة انتظار الطلبات.

ما لن يكون أصدقاء


لسوء الحظ ، هناك شيئان لا يمكن ربط رسائل الاستدعاء بهما بشكل صحيح. لمجرد أن الرسالة المنسحبة لا تزال في الطوابير حيث وصلت بالفعل.

message_limits


تم تصميم هذه الميزة الهامة من SObjectizer مثل message_limits لحماية الوكلاء من التحميل الزائد. تعمل خدمة Message_limits بناءً على عدد الرسائل في قائمة الانتظار. وضع رسالة في قائمة الانتظار - زاد العداد. خرجت عن الخط - مخفضة.

لأن عندما يتم إبطال رسالة ، فإنها تظل في قائمة الانتظار ، ثم لا تؤثر message_limits على استجابة الرسالة. لذلك ، قد يتبين أن قائمة الانتظار لها حد لعدد الرسائل من النوع M ، ولكن تم استدعاؤها جميعًا. في الواقع ، لن تتم معالجة أي منهم. لكن انتظار رسالة جديدة من النوع M لن ينجح ، لأن تم تجاوز الحد.

الحالة ليست جيدة. ولكن كيف نخرج منه؟ ليس من الواضح.

سلاسل انتظار طابور ثابتة


في SObjectizer ، يمكن إرسال رسالة ليس فقط إلى mbox ، ولكن أيضًا إلى mchain (هذا هو التناظرية لقناة CSP ). ويمكن أن يكون للمجموعات حجم ثابت لقوائم الانتظار الخاصة بها. يجب أن تؤدي محاولة وضع رسالة جديدة لـ mchain ذات حجم ثابت في mchain الكامل إلى نوع من التفاعل. على سبيل المثال ، انتظار تحرير مساحة في قائمة الانتظار. أو دفع الرسالة الأقدم.

في حالة استدعاء الرسالة ، ستظل داخل قائمة انتظار mchain. اتضح أن الرسالة لم تعد هناك حاجة إليها ، لكنها تشغل مساحة في قائمة انتظار mchain. ويمنع إرسال رسائل جديدة إلى mchain.

نفس الحالة السيئة كما هو الحال مع message_limits. ومرة أخرى ، ليس من الواضح كيف يمكن إصلاحه.

ما هو غير واضح كيف نفعل


لذلك وصلنا إلى الاختيار بين خيارين (حتى الآن؟) لتنفيذ رسائل الاستدعاء. الخيار الأول سهل التطبيق ولا يتطلب تغيير حوصلة SObjectizer. الخيار الثاني أكثر تعقيدًا بكثير ، ولكن فيه لا يعرف مستلم الرسالة أنه يتعامل مع رسائل قابلة للإلغاء. سننظر بإيجاز في كل واحد منهم.

تلقي رسائل قابلة للإلغاء مثل Revocable_t <M>


الحل الأول ، الذي يبدو ، أولاً ، ممكنًا ، وثانيًا ، عمليًا تمامًا ، هو إدخال غلاف خاص قابل للإبطال. عندما يرسل المستخدم رسالة قابلة للإلغاء من النوع M عبر revocable_handle_t <M> ، لا يتم إرسال الرسالة M ، ولكن الرسالة M داخل الغلاف الخاص Revocable_t <M>. وبناءً على ذلك ، لن يتلقى المستخدم ويعالج رسالة من النوع M ، ولكن الرسالة revocable_t <M>. على سبيل المثال ، بهذه الطريقة:

 class processor : public so_5::agent_t { public: struct request { ... }; // ,    . void so_define_agent() override { //   . so_subscribe_self().event( //     ,    //   . [this](mhood_t< revocable_t<request> > cmd) { // ,      . cmd->try_handle([this](mhood_t<request> msg) { ... }); }); ... } ... }; 

تتحقق طريقة revocable_t <M> :: try_handle () من قيمة العلامة الذرية ، وإذا لم يتم استدعاء الرسالة ، تستدعي وظيفة لامدا التي تم تمريرها إليها. إذا تم سحب الرسالة ، فلن تفعل try_handle () أي شيء.

إيجابيات وسلبيات هذا النهج


الميزة الرئيسية هي أن هذه الرحلة يتم تنفيذها بسهولة (على الأقل حتى الآن). في الواقع ، ستكون revocable_handle_t <M> و revocable_t <M> مجرد إضافة خفية إلى SObjectizer.

قد يكون التدخل في المكونات الداخلية لـ SObjectizer مطلوبًا لتكوين صداقات Revocable_t و mutable_msg. والحقيقة هي أنه في SObjectizer يوجد مفهوم الرسائل غير القابلة للتغيير (يمكن إرسالها في وضع 1: 1 وفي وضع 1: N). وهناك مفهوم الرسائل القابلة للتغيير التي لا يمكن إرسالها إلا في وضع 1: 1. في هذه الحالة ، يعامل SObjectizer بطريقة خاصة علامة mutable_msg <M> ويقوم بإجراء الفحوصات المقابلة في وقت التشغيل. في حالة revocable_t <mutable_msg <M>> ، ستحتاج إلى تعليم SObjectizer للتعامل مع هذه البنية على أنها mutable_msg <M>.

ميزة أخرى هي أن الحمل الإضافي الإضافي (سواء على البيانات الوصفية للرسالة القابلة للإلغاء وعلى التحقق من العلم الذري) سيكون فقط في الأماكن التي لا يمكنك الاستغناء عنها. عندما لا يتم استخدام رسائل الاستدعاء ، لن يكون هناك أي نفقات إضافية على الإطلاق.

لكن ناقص الرئيسي هو أيديولوجي. في هذا النهج ، تؤثر حقيقة استخدام الرسائل القابلة للإلغاء على المرسل (باستخدام revocable_handle_t <M>) والمستلم (باستخدام Revocable_t <M>). لكن المتلقي لا يحتاج إلى معرفة أنه يتلقى رسائل استدعاء. علاوة على ذلك ، بصفتك مستلمًا ، يمكن أن يكون لديك وكيل جهة خارجية جاهز مكتوب بدون Revocable_t <M>.

بالإضافة إلى ذلك ، تبقى الأسئلة الإيديولوجية حول ، على سبيل المثال ، حول إمكانية إعادة توجيه هذه الرسائل. ولكن ، وفقًا للتقديرات الأولى ، يتم حل هذه القضايا.

تلقي رسائل الاستدعاء كرسائل عادية


النهج الثاني هو رؤية رسالة النوع M فقط على جانب جهاز الاستقبال وليس لديها فكرة عن وجود revocable_handle_t <M> و revocable_t <M>. على سبيل المثال إذا كان المعالج يجب أن يتلقى طلبًا ، فيجب أن يرى طلبًا فقط ، بدون أي أغلفة إضافية.

في الواقع ، لا يمكن للمرء الاستغناء عن بعض الأغلفة في هذا النهج ، ولكن سيتم إخفاؤها داخل SObjectizer ويجب ألا يراها المستخدم. بعد استرداد التطبيق من قائمة الانتظار ، سيحدد SObjectizer أنها رسالة قابلة للإلغاء خاصة ملفوفة بشكل خاص ، وتحقق من إشارة صلة الرسالة ، وقم بتوسيع الرسالة إذا كانت لا تزال ذات صلة. ثم سترسل رسالة إلى الوكيل للمعالجة كما لو كانت رسالة عادية.

إيجابيات وسلبيات هذا النهج


الميزة الرئيسية لهذا النهج واضحة - لا يعرف مستلم الرسالة الرسائل التي يعمل معها. يسمح هذا لمرسل الرسالة بسحب الرسائل لأي وكلاء حتى بهدوء ، حتى تلك التي كتبها مطورين آخرين.

ميزة أخرى مهمة هي القدرة على التكامل مع آلية تتبع تسليم الرسائل ( هنا يتم وصف دور هذه الآلية بمزيد من التفصيل ). على سبيل المثال إذا تم تمكين msg_tracing وسحب المرسل الرسالة ، فيمكن العثور على آثار ذلك في سجل msg_tracing. وهو أمر مريح للغاية عند تصحيح الأخطاء.

لكن العيب الرئيسي هو تعقيد تنفيذ هذا النهج. حيث يجب أن تؤخذ في الاعتبار عدة عوامل.

أولاً ، النفقات العامة. كل أنواع الأشياء.

لنفترض أنه يمكنك إنشاء علامة خاصة داخل رسالة تشير إلى ما إذا كانت هذه الرسالة قابلة للإلغاء أم لا. ثم تحقق من هذه العلامة قبل البدء في معالجة كل رسالة. تحدث تقريبًا ، إذا تمت إضافة أخرى إلى آلية تسليم الرسائل ، والتي ستعمل أثناء معالجة كل رسالة (!).

أنا متأكد من أنه في التطبيقات الحقيقية لن تكون الخسارة على هذا ملحوظة. لكن الانخفاض في المعايير الاصطناعية سيظهر بالتأكيد. علاوة على ذلك ، كلما كان المعيار أكثر تجريدًا ، كلما قل العمل الحقيقي الذي يقوم به ، كلما غرق. وهذا أمر سيئ من وجهة نظر التسويق ، لأنه هناك عدد من الأفراد الذين يستخلصون استنتاجات حول الإطار من حيث المعايير الاصطناعية. ويقومون بذلك على وجه التحديد: عدم فهم أي نوع من المعايير هو أنه يوضح بشكل أساسي الأجهزة التي يعمل عليها ، ولكن مقارنة الإجماليات بأداء بعض الأدوات المتخصصة ، في سيناريو آخر ، على أجهزة أخرى ، إلخ. . ، إلخ.

بشكل عام ، نظرًا لأننا ننشئ إطارًا عامًا ، والذي ، كما يتضح ، يتم الحكم عليه من خلال الأرقام المجردة في المعايير المجردة ، لا نريد أن نفقد ، على سبيل المثال ، 5٪ من الأداء في آلية التسليم لجميع الرسائل بسبب إضافة ميزة تستغرق وقتًا فقط من وقت لآخر وليس لجميع المستخدمين.

لذلك ، تحتاج إلى التأكد من أنه عند إرسال الرسالة إلى المستلم ، يفهم SObjectizer أنه عند استخراج الرسالة ، تحتاج إلى التعامل معها بطريقة خاصة. من حيث المبدأ ، عندما يتم تسليم رسالة إلى وكيل ، يخزن SObjectizer مع الرسالة مؤشر إلى دالة سيتم استخدامها عند معالجة الرسالة. هذا مطلوب الآن للتعامل مع الرسائل غير المتزامنة والطلبات المتزامنة بطرق مختلفة. في الواقع ، هذه هي الطريقة التي يبدو بها طلب الرسالة الموجهة إلى الوكيل:

 struct execution_demand_t { //! Receiver of demand. agent_t * m_receiver; //! Optional message limit for that message. const message_limit::control_block_t * m_limit; //! ID of mbox. mbox_id_t m_mbox_id; //! Type of the message. std::type_index m_msg_type; //! Event incident. message_ref_t m_message_ref; //! Demand handler. demand_handler_pfn_t m_demand_handler; ... }; 

حيث يكون request_handler_pfn_t مؤشر دالة عادي:
 typedef void (*demand_handler_pfn_t)( current_thread_id_t, execution_demand_t & ); 

يمكن أيضًا استخدام نفس الآلية لمعالجة الرسالة التي يتم سحبها بشكل خاص. على سبيل المثال عندما يرسل mbox رسالة إلى الوكيل ، يعرف الوكيل ما إذا تم إرسال رسالة غير متزامنة أو طلب متزامن إليه. وبالمثل ، يمكن إعطاء وكيل رد اتصال غير متزامن بطريقة خاصة. وسيحفظ الوكيل ، مع الرسالة ، مؤشرًا لدالة تعرف كيف يجب أن تتعامل مع الرسائل التي تم إبطالها.

يبدو أن كل شيء على ما يرام ، ولكن هناك نوعان من "الاستثناءات" الكبيرة ... :(

أولاً ، لا تحتوي واجهة mbox الحالية (أي فئة abstract_message_mbox_t ) على طرق لإرسال رسائل الاستدعاء. لذا يجب توسيع هذه الواجهة. وحتى لا تنفجر تطبيقات mbox لأشخاص آخرين مرتبطة بـ abstract_message_box_t من SObjectizer-5.5 (على وجه الخصوص ، يتم تنفيذ سلسلة mbox في so_5_extra وأنا لا أريد كسرها).

ثانيًا ، يمكن إرسال الرسائل ليس فقط إلى mbox-s ، حيث يتم إخفاء الوكلاء خلفهم ، ولكن أيضًا إلى mchain-s. وهي نظيراتنا لقنوات CSP . وحتى الآن ، كانت التطبيقات تكذب دون أي مؤشرات إضافية للوظائف. لإدخال مؤشر إضافي في كل عنصر من عناصر قائمة انتظار التطبيق ... يمكنك ، بالطبع ، ولكن يبدو أنه حل مكلف إلى حد ما. بالإضافة إلى ذلك ، لم توفر تطبيقات mchain نفسها حتى الآن حالة تتطلب فحص الرسالة المستخرجة وربما التخلص منها.

إذا حاولت تلخيص جميع المشاكل الموضحة أعلاه ، فإن المشكلة الرئيسية لهذا النهج هي أنه ليس من السهل تنفيذ ذلك بحيث يكون رخيصًا في الحالات التي لا يتم فيها استخدام رسائل الاستدعاء.

ولكن ماذا عن الإلغاء المضمون للرسائل المعلقة؟


أخشى أن المشكلة الأصلية قد ضاعت في براري التفاصيل التقنية. لنفترض أن هناك رسائل قابلة للإلغاء ، كيف سيتم إلغاء الرسائل المعلقة / الدورية؟

هنا ، كما يقولون ، الخيارات ممكنة. على سبيل المثال ، قد يكون العمل مع الرسائل المعلقة / الدورية جزءًا من وظيفة revocable_handle_t <M>:

 revocable_handle_t<my_mesage> msg; msg.send_delayed(target, 15s, ...); ... msg.revoke(); 

أو يمكنك أن تجعل على رأس revocable_handle_t <M> فئة مساعدة إضافية قابلة للإلغاء قابلة للإلغاءimim_t_tMM ، والتي ستوفر طرق send_delayed / send_periodic.

بقعة بيضاء: طلبات متزامنة


لا يدعم SObjectizer-5 التفاعل غير المتزامن فقط بين الكيانات في البرنامج (عن طريق إرسال الرسائل إلى mbox و mchain) ، ولكن أيضًا التفاعل المتزامن من خلال request_value / request_future. هذا التفاعل المتزامن لا يعمل فقط للوكلاء. على سبيل المثاللا يمكنك فقط إرسال طلب متزامن إلى وكيل عبر mbox الخاص به. في حالة mchains ، يمكنك أيضًا إجراء طلبات متزامنة ، على سبيل المثال ، إلى مؤشر ترابط عامل آخر ، والذي تم فيه استدعاء () أو select () لاستدعاء mchain.

لذلك ، لا يزال من غير الواضح ما إذا كان يجب السماح باستخدام الطلبات المتزامنة مع الرسائل القابلة للإلغاء. من ناحية ، ربما هذا منطقي. وقد يبدو ، على سبيل المثال ، مثل هذا:

 revocable_handle_t<my_request> msg; auto f = msg.request_future<my_reply>(target, ...); ... if(some_condition) msg.revoke(); ... f.get(); //      revoke(). 

من ناحية أخرى ، لا يزال هناك الكثير من الرسائل غير المفهومة مع رسائل الاستدعاء ، لذلك تم تأجيل مشكلة التفاعل المتزامن حتى أوقات أفضل.

اختر ، ولكن كن حذرا. لكن اختر


لذلك هناك فهم للمشكلة. هناك خياران لحلها. الذي يبدو ممكناً في الوقت الحالي. ولكنها تختلف اختلافًا كبيرًا في مستوى الراحة التي يتم توفيرها للمستخدم ، بل وتختلف بشكل أكبر في تكلفة التنفيذ.

عليك أن تختار بين هذين الخيارين. أو الخروج بشيء آخر.

ما هي صعوبة الاختيار؟

الصعوبة هي أن SObjectizer هو إطار عمل حر. لا يجلب لنا المال مباشرة. نحن نفعل ذلك ، كما يقولون ، من أجلنا. لذلك ، من الخيارات الاقتصادية البحتة ، يكون الخيار الأبسط والأسرع للتنفيذ أكثر ربحية.

ولكن ، من ناحية أخرى ، لا يتم قياس كل شيء بالمال ، وعلى المدى الطويل ، فإن أداة جيدة الصنع ، ترتبط ميزاتها عادة ببعضها البعض ، أفضل من رقعة خليط مصنوعة من بقع عالقة معًا بطريقة ما. يتم تقييم الجودة من قبل المستخدمين وأنفسنا ، عندما نرافق تطويرنا لاحقًا ونضيف ميزات جديدة إليها.

لذا ، فإن الاختيار ، في الواقع ، يتنقل بين الفوائد قصيرة المدى والآفاق طويلة المدى. صحيح ، في العالم الحديث ، تكون أدوات C ++ ذات التوقعات طويلة المدى ضبابية إلى حد ما. مما يجعل الاختيار أكثر صعوبة.

في مثل هذه الظروف عليك أن تختار. تحذير لكن اختر.

الخلاصة


حاولنا في هذه المقالة أن نظهر قليلاً عملية تصميم وتنفيذ ميزات جديدة في إطار عملنا. تتم مثل هذه العملية بانتظام معنا. في وقت سابق بسبب في 2014-2016 طور SObjectizer بنشاط أكبر. الآن انخفضت وتيرة إصدار الإصدارات الجديدة. وهو أمر موضوعي ، بما في ذلك لأن إضافة وظائف جديدة دون كسر أي شيء ، يصبح أكثر صعوبة مع كل إصدار جديد.

آمل أن يكون من المثير للاهتمام أن ننظر إلينا من وراء الكواليس. شكرا لكم على اهتمامكم!

Source: https://habr.com/ru/post/ar423879/


All Articles