Apakah mudah untuk menambahkan fitur baru ke kerangka lama? Pilihan tepung pada contoh pengembangan SObjectizer



Mengembangkan kerangka kerja gratis untuk kebutuhan pengembang adalah topik khusus. Jika pada saat yang sama kerangka hidup dan berkembang untuk waktu yang agak lama, maka spesifik ditambahkan. Hari ini saya akan mencoba menunjukkan ini menggunakan contoh upaya untuk memperluas fungsionalitas kerangka kerja β€œaktor” untuk C ++ yang disebut SObjectizer .

Faktanya adalah bahwa kerangka kerja ini sudah cukup lama, telah berubah secara dramatis beberapa kali. Bahkan inkarnasinya saat ini, SObjectizer-5, telah mengalami banyak perubahan, baik yang serius maupun yang tidak. Selain itu, kami cukup peka terhadap kompatibilitas dan memperkenalkan perubahan yang merusak kompatibilitas terlalu serius bagi kami untuk memutuskannya.

Saat ini kita perlu memutuskan bagaimana menambahkan fitur baru ke versi berikutnya. Dalam proses menemukan solusi yang cocok, dua opsi muncul. Keduanya terlihat cukup bisa diwujudkan. Tetapi mereka sangat berbeda satu sama lain. Baik dari segi kompleksitas dan kompleksitas implementasi, dan dalam "penampilannya". Yaitu apa yang akan ditangani pengembang akan terlihat berbeda di setiap opsi. Mungkin bahkan berbeda secara mendasar.

Dan sekarang, sebagai pengembang kerangka kerja, kita harus membuat pilihan yang mendukung satu atau solusi lainnya. Atau kita harus mengakui bahwa tidak ada yang memuaskan dan, oleh karena itu, sesuatu yang lain perlu diciptakan. Keputusan seperti itu selama sejarah SObjectizer harus dibuat lebih dari sekali. Jika seseorang tertarik untuk merasakan perkembangan kerangka kerja seperti itu, maka Anda dapat melakukannya.

Masalah asli


Jadi, singkatkan esensi dari masalah aslinya. Sejak awal keberadaannya, SObjectizer memiliki fitur berikut: pesan penghitung waktu tidak mudah dibatalkan. Di bawah timer akan dipahami, pertama-tama, pesan yang tertunda. Yaitu pesan yang tidak boleh segera dikirim ke penerima, tetapi setelah beberapa waktu. Sebagai contoh, kami melakukan send_delay dengan jeda 1s. Ini berarti bahwa pada kenyataannya pesan akan dikirim oleh timer 1s setelah panggilan send_delayed.

Pesan yang tertunda dapat, pada dasarnya, dibatalkan. Jika pesan masih memiliki timer, maka pesan setelah pembatalan tidak akan pergi ke mana pun. Itu akan dilemparkan oleh timer dan hanya itu. Tetapi jika timer sudah mengirim pesan dan sekarang ada di antrian permintaan untuk agen penerima, maka membatalkan timer tidak akan berfungsi. Tidak ada mekanisme dalam SObjectizer untuk menghapus pesan dari antrian aplikasi.

Masalahnya diperparah oleh setidaknya dua faktor.

Pertama, SObjectizer mendukung pengiriman dalam mode 1: N, mis. jika pesan dikirim ke mbox Multi-Konsumen, maka pesan tidak akan berada dalam satu antrian, tetapi dalam beberapa antrian untuk penerima N sekaligus.

Kedua, SObjectizer menggunakan mekanisme dispatcher dan dispatcher bisa sangat berbeda, termasuk yang ditulis oleh pengguna untuk kebutuhan spesifik mereka. Antrian permintaan dikelola oleh operator. Dan di antarmuka dispatcher tidak ada fungsi untuk menarik aplikasi yang sudah ditransfer ke dispatcher. Tetapi bahkan jika fungsi tersebut tertanam dalam antarmuka, itu jauh dari kenyataan bahwa itu dapat diimplementasikan secara efektif dalam semua kasus. Belum lagi fakta bahwa fungsionalitas seperti itu akan meningkatkan kompleksitas pengembangan dispatcher baru.

Secara umum, secara objektif, jika timer telah mengirim pesan yang tertunda ke penerima, maka memaksa SObjectizer untuk tidak mengirimkan instance pesan ini saat ini adalah mustahil.
Faktanya, masalah ini juga relevan untuk pesan berkala (mis. Pesan yang harus dikirim timer secara berkala pada interval waktu yang telah ditentukan). Namun dalam praktiknya, membatalkan pesan berkala jauh lebih tidak penting daripada membatalkan pesan yang tertunda. Setidaknya dalam praktik kami begini.

Apa yang bisa dilakukan sekarang?


Jadi, masalah ini bukanlah hal yang baru dan untuk waktu yang lama ada rekomendasi tentang bagaimana menghadapinya.

Id unik di dalam pesan tertunda


Cara termudah adalah menjaga konter. Agen memiliki penghitung, saat mengirim pesan yang tertunda, nilai penghitung saat ini dikirim dalam pesan. Ketika pesan dibatalkan, penghitung di agen bertambah. Setelah menerima pesan, nilai penghitung saat ini di agen dibandingkan dengan nilai dari pesan. Jika nilainya tidak cocok, maka pesannya ditolak:

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_) { ... //  . } } }; 

Masalah dengan metode ini adalah bahwa pengembang agen perlu bingung dengan mempertahankan counter ini. Dan jika sebagai pesan tertunda kita perlu mengirim pesan orang lain bahwa orang lain melakukannya, dan di mana tidak ada bidang id_, maka kita menemukan diri kita dalam situasi yang sulit.

Meskipun, di sisi lain, ini adalah cara paling efektif yang ada saat ini.

Gunakan mbox unik untuk pesan yang tertunda


Cara lain yang berfungsi dengan baik adalah dengan menggunakan kotak surat unik (mbox) untuk pesan yang tertunda. Dalam hal ini, kami membuat mbox baru untuk setiap pesan yang tertunda, berlangganan dan mengirim pesan yang tertunda ke mbox ini. Ketika sebuah pesan perlu dibatalkan, maka kami cukup menghapus langganan 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) { //     ,   //    . ... } }; 

Metode ini sudah dapat berfungsi dengan pesan orang lain, di dalamnya tidak ada pengidentifikasi unik. Tetapi itu juga membutuhkan tenaga dan perhatian dari pengembang.

Misalnya, dalam perwujudan di atas, tidak ada perlindungan terhadap fakta bahwa satu pesan yang tertunda telah dikirim sebelumnya. Dengan cara yang baik, sebelum mengirim pesan tertunda yang baru, Anda harus selalu melakukan tindakan dari on_cancel_event (), jika tidak, agen akan memiliki langganan yang tidak perlu.

Mengapa masalah ini belum terpecahkan sebelumnya?


Semuanya cukup sederhana di sini: pada kenyataannya, ini bukan masalah serius seperti kelihatannya. Setidaknya dalam kehidupan nyata Anda tidak harus sering menghadapinya. Biasanya pesan yang tertunda dan berkala tidak dibatalkan sama sekali (oleh karena itu, fungsi send_delayed tidak mengembalikan timer_id). Dan ketika kebutuhan untuk pembatalan muncul, maka Anda dapat menggunakan salah satu metode yang dijelaskan di atas. Atau bahkan menggunakan yang lain. Misalnya, buat agen terpisah yang akan memproses pesan yang tertunda. Agen-agen ini dapat dideregistrasi ketika pesan yang tertunda perlu dibatalkan.

Jadi, dengan latar belakang tugas-tugas lain yang berhadapan dengan kami, menyederhanakan pembatalan pesan yang tertunda tidak diprioritaskan untuk menghabiskan sumber daya kami untuk menyelesaikan masalah ini.

Mengapa masalah ini relevan sekarang?


Semuanya sesederhana di sini. Di satu sisi, tangan akhirnya mencapai.

Di sisi lain, ketika orang baru yang tidak memiliki pengalaman bekerja dengannya mulai menggunakan SObjectizer, fitur ini dengan pembatalan timer sangat mengejutkan mereka. Tidak terlalu mengejutkan. Dan jika demikian, maka saya ingin meminimalkan kesan negatif untuk mengenal alat kami.

Selain itu, kami memiliki tugas kami sendiri, kami tidak perlu terus-menerus membatalkan pesan yang tertunda. Dan pengguna baru memiliki tugas mereka sendiri, mungkin semuanya sebaliknya.

Pernyataan masalah baru


Hampir segera, segera setelah pertimbangan kemungkinan "pembatalan waktu dijamin" dimulai, muncul pikiran di benak saya bahwa tugas itu dapat diperluas. Anda dapat mencoba untuk memecahkan masalah mengingat salah satu pesan yang dikirim sebelumnya, tidak harus ditunda dan berkala.

Dari waktu ke waktu kesempatan ini sangat diminati. Misalnya, bayangkan kami memiliki beberapa agen yang berinteraksi dari dua jenis: entry_point (menerima permintaan dari klien), dan prosesor (memproses permintaan):



Agen Entry_point mengirim permintaan ke agen prosesor, yang memprosesnya sebanyak mungkin dan merespons agen entry_point. Tetapi kadang-kadang, entry_point mungkin menemukan bahwa memproses permintaan yang dikirim sebelumnya tidak lagi diperlukan. Misalnya, klien mengirim perintah pembatalan atau klien "jatuh" dan Anda tidak perlu lagi memproses permintaannya. Sekarang, jika pesan permintaan diantrekan oleh agen prosesor, maka Anda tidak dapat mengingatnya. Dan itu akan bermanfaat.

Oleh karena itu, pendekatan saat ini untuk memecahkan masalah "pembatalan waktu yang dijamin" dilakukan tepat seperti menambahkan dukungan untuk "pesan penarikan kembali". Kami mengirim pesan apa pun dengan cara khusus, kami dapat menangani, yang dengannya Anda dapat mengingat pesan tersebut. Dan itu tidak begitu penting apakah pesan biasa atau yang ditunda merespons.

Upaya untuk memunculkan implementasi "recall message"


Jadi, Anda perlu memperkenalkan konsep "recall message" dan mendukung konsep ini di SObjectizer. Jadi, untuk tetap dalam cabang 5,5. Versi pertama dari utas ini, 5.5.0, keluar hampir empat tahun lalu, pada Oktober 2014. Sejak itu, belum ada perubahan besar yang terjadi pada 5.5. Proyek yang sudah beralih atau segera mulai pada SObjectize-5.5 dapat beralih ke rilis baru di cabang 5.5 tanpa masalah. Kompatibilitas ini harus dipertahankan saat ini.

Secara umum, semuanya sederhana: Anda perlu mengambil dan melakukan.

Yang jelas gimana caranya


Setelah pendekatan pertama ke masalah, dua hal menjadi jelas tentang implementasi "pesan recall".

Bendera atom dan verifikasi sebelum pemrosesan pesan


Pertama, jelas bahwa dalam kerangka arsitektur SObjectizer-5.5 saat ini (dan mungkin bahkan lebih global: dalam kerangka prinsip-prinsip SObjectizer-5 itu sendiri), tidak mungkin untuk menghapus pesan dari antrian permintaan dispatcher, di mana pesan menunggu sampai agen penerima memprosesnya. Mencoba melakukan ini akan mematikan seluruh ide para pengirim yang heterogen, yang bahkan dapat dilakukan sendiri oleh pengguna, sesuai dengan spesifikasi tugas mereka (misalnya, yang ini ). Selain itu, dalam hal mengirim pesan dalam mode 1: N, di mana N akan besar, akan mahal untuk menyimpan daftar petunjuk ke instance dari pesan yang dikirim di semua antrian.

Ini berarti bahwa bersama dengan pesan, beberapa jenis bendera atom harus ditransmisikan, yang perlu dianalisis segera setelah pesan dihapus dari antrian permintaan, tetapi sebelum pesan dikirim untuk diproses ke agen penerima. Yaitu pesan memasuki antrian dan tidak dihapus di mana pun dari sana. Tetapi ketika giliran datang ke pesan, benderanya diperiksa. Dan jika bendera mengatakan bahwa pesan telah ditarik, maka pesan tersebut tidak diproses.

Karenanya, penarikan kembali pesan itu sendiri terdiri dari pengaturan nilai khusus untuk bendera atom di dalam pesan.

Revocable_handle_t <M> objek


Kedua, sejauh ini (?) Jelas bahwa untuk mengirim pesan yang dapat dibatalkan, bukan metode biasa untuk mengirim pesan yang harus digunakan, tetapi objek khusus di bawah nama kondisional revocable_handle_t.

Untuk mengirim pesan yang dapat dibatalkan, pengguna harus membuat instance dari revocable_handle_t, dan kemudian memanggil metode kirim pada instance ini. Dan jika pesan perlu dipanggil kembali, maka ini dilakukan dengan menggunakan metode pencabutan. Sesuatu seperti:

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

Belum ada detail yang jelas dari implementasi revocable_handle_t, yang tidak mengherankan mekanisme kerja pesan recall belum dipilih. Tetapi prinsip kerjanya adalah bahwa di revocable_handle_t tautan pintar disimpan ke pesan yang dikirim dan ke bendera atom untuknya. Metode pencabutan () mencoba untuk mengganti nilai bendera. Jika ini berhasil, maka pesan, setelah diekstraksi dari antrian pesanan, tidak akan lagi diproses.

Apa yang tidak akan berteman dengan


Sayangnya, ada beberapa hal yang membuat pesan yang ditarik tidak dapat ditautkan dengan benar. Hanya karena pesan yang ditarik terus tetap dalam antrian di mana pesan itu telah tiba.

message_limits


Fitur SObjectizer yang penting seperti message_limits dirancang untuk melindungi agen dari kelebihan muatan. Message_limits berfungsi berdasarkan jumlah pesan dalam antrian. Antrian pesan - menambah penghitung. Keluar jalur - dikurangi.

Karena ketika pesan dicabut, ia masih berada dalam antrian, maka message_limits tidak memengaruhi respons pesan. Oleh karena itu, ternyata antrian memiliki batas jumlah pesan tipe M, tetapi semuanya telah dipanggil kembali. Bahkan, tidak satu pun dari mereka akan diproses. Tetapi mengantri pesan baru tipe M tidak akan berhasil, karena batas terlampaui.

Situasinya tidak bagus. Tapi bagaimana cara keluar dari situ? Tidak jelas.

memperbaiki antrian mchains


Di SObjectizer, sebuah pesan dapat dikirim tidak hanya ke mbox, tetapi juga ke mchain (ini adalah analog dari saluran CSP kami ). Dan rantai dapat memiliki ukuran tetap untuk antrian mereka. Upaya untuk menempatkan pesan baru untuk mchain dengan ukuran tetap di mchain penuh harus mengarah pada semacam reaksi. Misalnya, menunggu keluarnya ruang dalam antrian. Atau untuk mendorong pesan tertua.

Dalam kasus penarikan pesan, pesan itu akan tetap berada di dalam antrian mchain. Ternyata pesan itu tidak lagi diperlukan, tetapi membutuhkan ruang di antrian mchain. Dan mencegah pesan baru dikirim ke mchain.

Situasi buruk yang sama dengan message_limits. Dan lagi, tidak jelas bagaimana itu bisa diperbaiki.

Yang tidak jelas gimana caranya


Jadi kita sampai pada pilihan antara dua (sejauh ini?) Opsi untuk mengimplementasikan pesan recall. Opsi pertama adalah sederhana untuk diimplementasikan dan tidak memerlukan perubahan jeroan ayam itik dari SObjectizer. Opsi kedua jauh lebih rumit, tetapi di dalamnya penerima pesan bahkan tidak tahu bahwa ia berurusan dengan pesan yang dapat dibatalkan. Kami akan mempertimbangkan secara singkat masing-masing dari mereka.

Terima pesan yang dapat dibatalkan sebagai revocable_t <M>


Solusi pertama, yang terlihat, pertama, layak dan, kedua, cukup praktis, adalah pengenalan pembungkus khusus revocable_t <M>. Saat pengguna mengirimkan pesan tipe M yang dapat dibatalkan melalui revocable_handle_t <M>, itu bukan pesan M yang dikirim, tetapi pesan M di dalam pembungkus khusus revocable_t <M>. Dan, karenanya, pengguna tidak akan menerima dan memproses pesan bertipe M, tetapi pesan revocable_t <M>. Misalnya, dengan cara ini:

 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) { ... }); }); ... } ... }; 

Metode revocable_t <M> :: try_handle () memeriksa nilai flag atom dan, jika pesan tersebut tidak dipanggil, panggilan fungsi lambda diteruskan ke sana. Jika pesan ditarik, maka try_handle () tidak melakukan apa-apa.

Pro dan kontra dari pendekatan ini


Kelebihan utama adalah bahwa perjalanan ini mudah diimplementasikan (setidaknya sejauh ini tampaknya). Faktanya, revocable_handle_t <M> dan revocable_t <M> hanya akan menjadi tambahan yang halus untuk SObjectizer.

Intervensi dalam SObjectizer internal mungkin diperlukan untuk membuat teman revocable_t dan mutable_msg. Faktanya adalah bahwa dalam SObjectizer ada konsep pesan tidak berubah (mereka dapat dikirim baik dalam mode 1: 1 dan dalam mode 1: N). Dan ada konsep pesan yang bisa diubah yang hanya bisa dikirim dalam mode 1: 1. Dalam hal ini, SObjectizer dengan cara khusus memperlakukan marker mutable_msg <M> dan melakukan pemeriksaan terkait pada saat run-time. Dalam kasus revocable_t <mutable_msg <M>>, Anda perlu mengajari SObjectizer untuk memperlakukan konstruk ini sebagai mutable_msg <M>.

Kelebihan lainnya adalah bahwa overhead tambahan (baik pada metadata dari pesan yang dapat dibatalkan dan pada verifikasi bendera atom) hanya akan berada di tempat di mana Anda tidak dapat melakukannya tanpa itu. Jika pesan penarikan tidak digunakan, tidak akan ada overhead tambahan sama sekali.

Tetapi minus utama adalah ideologis. Dalam pendekatan ini, fakta menggunakan pesan yang dapat dibatalkan mempengaruhi pengirim (menggunakan revocable_handle_t <M>) dan penerima (menggunakan revocable_t <M>). Tetapi penerima hanya tidak perlu tahu bahwa ia menerima pesan penarikan. Selain itu, sebagai penerima, Anda dapat memiliki agen pihak ketiga yang sudah jadi yang ditulis tanpa revocable_t <M>.

Selain itu, pertanyaan ideologis tetap tentang, misalnya, kemungkinan meneruskan pesan tersebut. Tetapi, menurut perkiraan pertama, masalah ini diselesaikan.

Terima pesan recall sebagai pesan biasa


Pendekatan kedua adalah hanya melihat pesan tipe M di sisi penerima dan tidak memiliki gagasan tentang keberadaan revocable_handle_t <M> dan revocable_t <M>. Yaitu jika prosesor harus menerima permintaan, maka itu hanya akan melihat permintaan, tanpa pembungkus tambahan.

Sebenarnya, seseorang tidak dapat melakukannya tanpa pembungkus dalam pendekatan ini, tetapi mereka akan disembunyikan di dalam SObjectizer dan pengguna seharusnya tidak melihatnya. Setelah aplikasi diambil dari antrian, SObjectizer akan menentukan sendiri bahwa ini adalah pesan yang dapat dibatalkan yang dibungkus khusus, memeriksa tanda relevansi pesan, dan memperluas pesan jika masih relevan. Kemudian akan mengirim pesan ke agen untuk diproses seolah-olah itu adalah pesan biasa.

Pro dan kontra dari pendekatan ini


Keuntungan utama dari pendekatan ini jelas - penerima pesan tidak tahu pesan apa yang ia gunakan. Ini memungkinkan pengirim pesan menarik pesan dengan tenang untuk agen apa pun, bahkan yang ditulis oleh pengembang lain.

Kelebihan penting lainnya adalah kemampuan untuk berintegrasi dengan mekanisme penelusuran pengiriman pesan (di sini peran mekanisme ini dijelaskan secara lebih rinci ). Yaitu jika msg_tracing diaktifkan dan pengirim menarik pesan, maka jejak ini dapat ditemukan di log msg_tracing. Yang sangat nyaman saat debugging.

Tetapi kelemahan utama adalah kompleksitas penerapan pendekatan ini. Di mana beberapa faktor perlu diperhitungkan.

Pertama, overhead. Segala macam hal.

Katakanlah Anda dapat membuat panji khusus di dalam pesan yang menunjukkan apakah pesan ini dapat dibatalkan atau tidak. Dan kemudian periksa bendera ini sebelum mulai memproses setiap pesan. Secara kasar, lain jika ditambahkan ke mekanisme pengiriman pesan, yang akan berfungsi saat memproses masing-masing (!) Pesan.

Saya yakin bahwa dalam aplikasi nyata kerugian ini jika hampir tidak terlihat. Namun drawdown pada benchmark sintetis pasti akan muncul. Selain itu, semakin abstrak tolok ukurnya, semakin sedikit pekerjaan yang dia lakukan, semakin dia akan tenggelam. Dan ini buruk dari sudut pandang pemasaran, karena ada sejumlah individu yang menarik kesimpulan tentang kerangka kerja dalam hal tolok ukur sintetis. Dan mereka melakukannya secara spesifik: tidak memahami apa jenis tolok ukurnya, bahwa itu pada dasarnya menunjukkan perangkat keras mana yang bekerja, tetapi membandingkan total dengan kinerja beberapa alat khusus, dalam skenario lain, pada perangkat keras lain, dll. , dll.

Secara umum, karena kami membuat kerangka kerja universal, yang ternyata dinilai dengan angka abstrak dalam tolok ukur abstrak, kami tidak ingin kehilangan, katakanlah, 5% dari kinerja dalam mekanisme pengiriman semua pesan karena penambahan fitur yang hanya membutuhkan waktu dari waktu ke waktu dan tidak untuk semua pengguna.

Oleh karena itu, Anda perlu memastikan bahwa ketika mengirim pesan ke penerima, SObjectizer memahami bahwa ketika Anda mengekstrak pesan, Anda perlu menanganinya dengan cara khusus. Pada prinsipnya, ketika pesan dikirim ke agen, SObjectizer menyimpan dengan pesan penunjuk ke fungsi yang akan digunakan saat memproses pesan. Ini diperlukan sekarang untuk menangani pesan asinkron dan permintaan sinkron dengan berbagai cara. Sebenarnya, beginilah tampilan permintaan untuk pesan yang ditujukan kepada agen:

 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; ... }; 

Di mana demand_handler_pfn_t adalah pointer fungsi reguler:
 typedef void (*demand_handler_pfn_t)( current_thread_id_t, execution_demand_t & ); 

Mekanisme yang sama juga dapat digunakan untuk secara khusus memproses pesan yang ditarik. Yaitu ketika mbox mengirim pesan ke agen, agen tahu apakah pesan asinkron atau permintaan sinkron dikirim ke sana. Demikian pula, agen dapat diberi pesan panggilan balik tidak sinkron dengan cara khusus. Dan agen akan menyimpan, bersama dengan pesan, sebuah penunjuk ke fungsi yang tahu bagaimana seharusnya menangani pesan yang dicabut.

Semuanya tampak baik-baik saja, tetapi ada dua "tapi" besar ... :(

Pertama, antarmuka mbox yang ada (yaitu, abstract_message_mbox_t class) tidak memiliki metode untuk mengirim pesan recall. Jadi antarmuka ini perlu diperluas. Dan agar implementasi mbox orang lain yang terikat ke abstract_message_box_t dari SObjectizer-5.5 tidak rusak (khususnya, seri mbox diimplementasikan dalam so_5_extra dan saya tidak ingin merusaknya ).

Kedua, pesan dapat dikirim tidak hanya ke mbox-s, di belakang agen mana yang disembunyikan, tetapi juga ke mchain-s. Yang merupakan mitra kami ke saluran CSP . Dan sampai sekarang, aplikasi itu berbohong tanpa petunjuk tambahan ke fungsi. Untuk memperkenalkan pointer tambahan ke setiap elemen mchain antrian aplikasi ... Anda tentu saja bisa, tetapi sepertinya solusi yang agak mahal. Selain itu, implementasi mchain sendiri belum menyediakan situasi di mana pesan yang diekstraksi perlu diperiksa dan mungkin dibuang.

Jika Anda mencoba merangkum semua masalah yang dijelaskan di atas, masalah utama dari pendekatan ini adalah tidak begitu mudah untuk membuat implementasinya sehingga lebih murah untuk kasus-kasus ketika pesan recall tidak digunakan.

Tapi bagaimana dengan pembatalan dijamin pesan tertunda?


Saya khawatir masalah aslinya telah hilang di belantara detail teknis. Misalkan ada pesan yang dapat dibatalkan, bagaimana pembatalan pesan yang tertunda / berkala akan terjadi?

Di sini, seperti yang mereka katakan, opsi dimungkinkan. Misalnya, bekerja dengan pesan tertunda / berkala dapat menjadi bagian dari fungsionalitas revocable_handle_t <M>:

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

Atau Anda dapat membuat di atas revocable_handle_t <M> kelas pembantu tambahan cancelable_timer_t <M>, yang akan memberikan metode send_delayed / send_ periodic.

Bintik putih: permintaan sinkron


SObjectizer-5 mendukung tidak hanya interaksi asinkron antara entitas dalam program (dengan mengirim pesan ke mbox dan mchain), tetapi juga interaksi sinkron melalui request_value / request_future. Interaksi yang sinkron ini tidak hanya berfungsi untuk agen. YaituAnda tidak hanya dapat mengirim permintaan sinkron ke agen melalui mbox-nya. Dalam kasus mchains, Anda juga dapat membuat permintaan sinkron, misalnya, ke utas kerja lainnya, yang menerima () atau pilih () dipanggil untuk mchain.

Jadi, masih belum jelas apakah harus diizinkan untuk menggunakan permintaan sinkron bersama dengan pesan yang dapat dibatalkan. Di satu sisi, mungkin ini masuk akal. Dan mungkin terlihat, misalnya, seperti ini:

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

Di sisi lain, masih ada banyak pesan yang tidak dapat dipahami dengan pesan recall, sehingga masalah interaksi sinkron telah ditunda hingga waktu yang lebih baik.

Pilih, tapi hati-hati. Tapi pilih


Jadi ada pemahaman tentang masalahnya. Ada dua opsi untuk menyelesaikannya. Yang saat ini sepertinya layak. Tetapi mereka sangat berbeda dalam tingkat kenyamanan yang diberikan kepada pengguna, dan bahkan lebih kuat mereka berbeda dalam biaya implementasi.

Anda harus memilih di antara dua opsi ini. Atau menghasilkan sesuatu yang lain.

Apa kesulitan memilih?

Kesulitannya adalah bahwa SObjectizer adalah kerangka kerja gratis. Dia tidak langsung membawa kita uang. Kita melakukannya, seperti kata mereka, untuk kita sendiri. Oleh karena itu, murni dari preferensi ekonomi, opsi yang lebih sederhana dan lebih cepat untuk diterapkan lebih menguntungkan.

Tetapi, di sisi lain, tidak semuanya diukur dalam uang, dan dalam jangka panjang, alat yang dibuat dengan baik, fitur-fitur yang biasanya terhubung satu sama lain, lebih baik daripada tambal sulam yang terbuat dari tambalan yang saling menempel entah bagaimana. Kualitas dievaluasi oleh pengguna dan diri kami sendiri, ketika kami selanjutnya menemani pengembangan kami dan menambahkan fitur baru ke dalamnya.

Jadi, pilihannya, pada kenyataannya, terjadi antara manfaat jangka pendek dan prospek jangka panjang. Benar, di dunia modern, alat C ++ dengan prospek jangka panjang entah bagaimana berkabut. Yang membuat pilihan semakin sulit.

Dalam kondisi seperti itulah Anda harus memilih. Perhatian Tapi pilih.

Kesimpulan


Pada artikel ini kami mencoba menunjukkan sedikit proses mendesain dan mengimplementasikan fitur-fitur baru dalam kerangka kerja kami. Proses semacam itu terjadi secara teratur bersama kami. Sebelumnya sering karena Pada 2014-2016 SObjectizer berkembang jauh lebih aktif. Sekarang laju rilis versi baru telah menurun. Yang objektif, termasuk karena menambahkan fungsionalitas baru tanpa merusak apa pun, menjadi lebih sulit dengan setiap versi baru.

Saya harap menarik untuk melihat di belakang layar kepada kami. Terima kasih atas perhatian anda!

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


All Articles