Baru di SObjectizer-5.5.23: keinginan terpenuhi atau kotak Pandora?



Artikel ini merupakan kelanjutan dari artikel refleksi yang diterbitkan sebulan yang lalu, " Apakah mudah untuk menambahkan fitur baru ke kerangka lama? Penderitaan pilihan berdasarkan contoh pengembangan SObjectizer ". Artikel itu menjelaskan tugas yang ingin kami selesaikan dalam versi SObjectizer berikutnya, memeriksa dua pendekatan untuk solusinya dan mendaftarkan kelebihan dan kekurangan masing-masing pendekatan.

Seiring berjalannya waktu, salah satu pendekatan diimplementasikan dan versi baru dari SObjectizer , serta proyek yang menyertainya so_5_extra , sudah disebut " bernapas dalam-dalam ". Anda dapat benar-benar mengambil dan mencoba.

Hari ini kita akan berbicara tentang apa yang dilakukan, mengapa itu dilakukan, apa yang menyebabkannya. Jika seseorang tertarik mengikuti bagaimana salah satu dari sedikit kerangka kerja aktor hidup, lintas platform, dan terbuka untuk C ++ berkembang, Anda dipersilakan untuk melakukannya.

Bagaimana semuanya dimulai?


Semuanya dimulai dengan upaya untuk memecahkan masalah pembatalan pengatur waktu yang dijamin. Inti dari masalah adalah bahwa ketika pesan tertunda atau berkala dikirim, programmer dapat membatalkan pengiriman pesan. Sebagai contoh:

auto timer_id = so_5::send_periodic<my_message>(my_agent, 10s, 10s, ...); ... // - . // ,    my_message    . timer_id.release(); //      my_message. 

Setelah memanggil timer_id.release (), timer tidak akan lagi mengirim instance baru dari pesan my_message. Tetapi salinan yang sudah dikirim dan berada dalam antrian penerima tidak akan pergi ke mana pun. Seiring waktu, mereka akan diekstraksi dari antrian yang sama dan akan ditransfer ke agen penerima untuk diproses.

Masalah ini merupakan konsekuensi dari prinsip dasar pengoperasian SObjectizer-5 dan tidak memiliki solusi sederhana karena fakta bahwa SObjectizer tidak dapat mengekstraksi pesan dari antrian. Itu tidak bisa karena antrian dalam SObjectizer milik dispatcher, dispatcher berbeda, antrian mereka juga diatur secara berbeda. Termasuk ada dispatcher yang bukan bagian dari SObjectizer, dan SObjectizer, pada prinsipnya, tidak dapat mengetahui cara kerja dispatcher ini.

Secara umum, ada fitur seperti itu di timer SObjectizer asli. Bukan berarti itu terlalu banyak merusak pengembang. Tetapi perhatian ekstra harus diambil. Terutama untuk pemula yang baru saja berkenalan dengan framework.

Dan akhirnya, tangan-tangan itu pergi ke titik mengusulkan solusi untuk masalah ini.

Jalur solusi mana yang dipilih?


Dalam artikel sebelumnya , dua opsi yang mungkin dipertimbangkan. Opsi pertama tidak memerlukan modifikasi pada mekanisme pengiriman pesan di SObjectizer, tetapi mengharuskan programmer untuk secara eksplisit mengubah jenis pesan yang dikirim / diterima.

Opsi kedua membutuhkan modifikasi dari mekanisme pengiriman pesan SObjectizer. Jalur inilah yang dipilih, karena memungkinkan bersembunyi dari penerima pesan fakta bahwa pesan dikirim dengan cara tertentu.

Apa yang telah berubah di SObjectizer?


Konsep baru: amplop dengan pesan di dalamnya


Komponen pertama dari solusi yang diimplementasikan adalah penambahan konsep seperti amplop ke SObjectizer. Amplop adalah pesan khusus, di dalamnya terdapat pesan saat ini (payload). SObjectizer mengirimkan amplop beserta pesannya ke penerima dengan cara yang hampir biasa. Perbedaan mendasar dalam pemrosesan amplop hanya terdeteksi pada tahap pengiriman terakhir:

  • setelah pengiriman pesan reguler, agen penerima hanya mencari penangan untuk jenis pesan ini dan, jika penangan seperti itu ditemukan, penangan yang ditemukan dipanggil dan pesan yang dikirim dikembalikan sebagai parameter;
  • dan setelah pengiriman amplop dengan pesan setelah pawang ditemukan, upaya pertama-tama dilakukan untuk mengeluarkan pesan dari amplop. Dan hanya jika amplop memberikan pesan yang tersimpan di dalamnya, hanya pawang yang dipanggil.

Ada dua poin utama di sini yang memiliki dampak besar pada mengapa dan bagaimana amplop pesan dapat digunakan.

Titik kunci pertama adalah bahwa pesan diminta dari amplop hanya ketika penangan pesan ditemukan di penerima. Yaitu hanya ketika pesan telah benar-benar dikirim ke penerima dan penerima akan ada di sini dan sekarang akan memproses pesan ini.

Poin kunci kedua di sini adalah bahwa amplop mungkin tidak memberikan pesan di dalamnya. Misalnya, sebuah amplop dapat memeriksa waktu saat ini dan memutuskan bahwa semua tanggal pengiriman telah terjawab dan, oleh karena itu, pesan tidak lagi relevan dan tidak dapat diproses. Karena itu, amplop tidak akan memberikan pesan. Karenanya, SObjectizer akan mengabaikan amplop ini dan tidak akan mengambil tindakan tambahan.

Seperti apa amplop itu?


Amplop adalah implementasi dari antarmuka envelope_t, yang didefinisikan sebagai berikut:

 class SO_5_TYPE envelope_t : public message_t { public: ... // -. //   ,       //    . virtual void handler_found_hook( handler_invoker_t & invoker ) noexcept = 0; //   ,      //     . virtual void transformation_hook( handler_invoker_t & invoker ) noexcept = 0; private : kind_t so5_message_kind() const noexcept override { return kind_t::enveloped_msg; } }; 

Yaitu Amplop pada dasarnya adalah pesan yang sama dengan yang lainnya. Tetapi dengan atribut khusus, yang dikembalikan oleh metode so5_message_kind ().

Programmer dapat mengembangkan amplop yang diwarisi dari envelope_t (atau, lebih mudahnya, dari so_5 :: extra :: enveloped_msg :: just_envelope_t ) dan mengganti metode hook handler_found_hook () dan transformation_hook ().

Di dalam metode kait, pengembang amplop memutuskan apakah ia ingin memberikan pesan di dalam amplop untuk diproses / diubah atau tidak. Jika dia mau, maka pengembang harus memanggil metode invoke () dan objek invoker. Jika dia tidak mau, dia tidak menelepon, dalam hal ini amplop dan isinya akan diabaikan.

Bagaimana cara amplop mengatasi masalah pembatalan pengatur waktu?


Solusinya, yang sekarang diimplementasikan dalam so_5_extra dalam bentuk namespace so_5 :: extra :: revocable_timer, sangat sederhana: pengiriman khusus pesan tertunda atau berkala menciptakan amplop khusus, di dalamnya terletak tidak hanya pesan itu sendiri, tetapi juga bendera atom dicabut. Jika bendera ini dihapus, maka pesan tersebut dianggap relevan. Jika diatur, maka pesan tersebut dianggap ditarik.

Ketika metode kait dipanggil pada amplop, amplop memeriksa nilai bendera yang dicabut. Jika bendera disetel, amplop tidak memberikan pesan. Dengan demikian, pesan tidak diproses bahkan jika timer sudah berhasil memasukkan pesan ke dalam antrian penerima.

Ekstensi antarmuka abstract_message_box_t


Menambahkan antarmuka envelope_t hanyalah salah satu bagian dari implementasi amplop di SObjectizer. Bagian kedua mempertimbangkan fakta adanya amplop dalam mekanisme pengiriman pesan di dalam SObjectizer.

Di sini, sayangnya, tidak bisa melakukan tanpa membuat perubahan terlihat oleh pengguna. Secara khusus, di kelas abstract_message_box_t, yang mendefinisikan antarmuka semua kotak surat di SObjectizer, perlu menambahkan metode virtual lain:

 virtual void do_deliver_enveloped_msg( const std::type_index & msg_type, const message_ref_t & message, unsigned int overlimit_reaction_deep ); 

Metode ini bertanggung jawab untuk mengirimkan amplop pesan dengan pesan tipe msg_type ke penerima. Pengiriman tersebut mungkin berbeda dalam detail implementasi tergantung pada jenis mbox itu.

Saat menambahkan do_deliver_enveloped_msg () ke abstract_message_box_t, kami punya pilihan: menjadikannya metode virtual murni atau menawarkan semacam implementasi standar.

Jika kami membuat do_deliver_enveloped_msg () metode virtual murni, maka kami akan memutus kompatibilitas antara versi SObjectizer di cabang 5.5. Setelah semua, maka para pengguna yang menulis implementasi mbox mereka sendiri harus memodifikasi mbox mereka sendiri ketika beralih ke SObjectizer-5.5.23, jika tidak mereka tidak akan dapat mengkompilasi dengan versi baru dari SObjectizer.

Kami tidak menginginkan ini, jadi kami tidak membuat do_deliver_enveloped_msg () metode virtual murni di v.5.5.23. Ini memiliki implementasi standar yang hanya melempar pengecualian. Dengan demikian, pengguna khusus mbox-s akan dapat terus bekerja secara normal dengan pesan biasa, tetapi secara otomatis akan menolak untuk menerima amplop. Kami menemukan perilaku ini lebih dapat diterima. Selain itu, pada tahap awal tidak mungkin bahwa amplop dengan pesan akan digunakan secara luas, dan tidak mungkin bahwa dalam implementasi kustom "liar" mbox SObjectizer sering ditemukan;)

Selain itu, ada jauh dari peluang nol bahwa dalam versi utama SObjectizer berikutnya, di mana kita tidak akan melihat kompatibilitas dengan cabang 5.5, antarmuka abstract_message_box_t akan mengalami perubahan besar. Tapi kita sudah maju dari diri kita sendiri ...

Cara mengirim amplop dengan pesan


SObjectizer-5.5.23 sendiri tidak menyediakan cara sederhana untuk mengirim amplop. Diasumsikan bahwa jenis spesifik amplop dan alat-alat yang sesuai sedang dikembangkan untuk tugas tertentu agar dapat dengan mudah mengirim amplop dari jenis tertentu. Contohnya dapat dilihat di so_5 :: extra :: revocable_timer , di mana Anda tidak hanya perlu mengirim amplop, tetapi juga memberi pengguna timer_id khusus.

Untuk situasi yang lebih sederhana, Anda dapat menggunakan alat dari so_5 :: extra :: enveloped_msg . Sebagai contoh, ini adalah bagaimana pesan dikirim dengan batasan yang diberikan pada waktu pengiriman:

 // make     . so_5::extra::enveloped_msg::make<my_message>(... /*    */) // envelope         . //  5s        . .envelope<so_5::extra::enveloped_msg::time_limited_delivery_t>(5s) //        . .send_to(destination); 

Untuk membuat semuanya menyenangkan: amplop dalam amplop


Amplop dirancang untuk membawa beberapa pesan di dalamnya. Tapi yang mana?

Apa saja.

Dan ini membawa kita pada pertanyaan yang menarik: apakah mungkin memasukkan amplop ke dalam amplop lain?

Ya kamu bisa. Sebanyak yang kamu suka. Kedalaman bersarang dibatasi hanya oleh akal sehat pengembang dan kedalaman tumpukan untuk panggilan handler_found_hook / transformation_hook panggilan rekursif.

Pada saat yang sama, SObjectizer pergi ke pengembang amplop sendiri: amplop tidak boleh berpikir tentang apa yang ada di dalamnya - pesan tertentu atau amplop lain. Ketika metode kait dipanggil pada amplop dan amplop memutuskan bahwa ia dapat memberikan isinya, amplop tersebut hanya memanggil invoke () pada handler_invoker_t dan meneruskan tautan ke isinya dalam invoke (). Dan sudah memanggil () di dalam akan mencari tahu apa yang berhadapan dengannya. Dan jika ini adalah amplop lain, maka aktifkan () akan memanggil metode kait yang diperlukan pada amplop ini.

Menggunakan toolkit yang ditunjukkan di atas dari so_5 :: extra :: enveloped_msg pengguna dapat membuat beberapa amplop bersarang seperti ini:

 so_5::extra::enveloped_msg::make<my_message>(...) // ,        my_message. .envelope<inner_envelope_type>(...) // ,      inner_envelope_type. .envelope<outer_envelope_type>(...) .send_to(destination); 

Beberapa contoh menggunakan amplop


Sekarang, setelah kita menjelajahi internal SObjectizer-5.5.23, sekarang saatnya untuk beralih ke bagian aplikasi yang lebih berguna bagi pengguna. Berikut adalah beberapa contoh yang didasarkan pada apa yang sudah diterapkan di so_5_extra, atau menggunakan alat dari so_5_extra.

Timer yang dapat dibatalkan


Karena seluruh dapur dengan amplop ini disusun untuk menyelesaikan masalah penarikan kembali pengingat waktu, mari kita lihat apa yang terjadi pada akhirnya. Kami akan menggunakan contoh dari so_5_extra-1.2.0, yang menggunakan alat dari so_5 :: extra :: revocable_timer namespace baru:

Contoh kode dengan penghitung waktu yang dapat dibatalkan
 #include <so_5_extra/revocable_timer/pub.hpp> #include <so_5/all.hpp> namespace timer_ns = so_5::extra::revocable_timer; class example_t final : public so_5::agent_t { //  ,       //    . struct first_delayed final : public so_5::signal_t {}; struct second_delayed final : public so_5::signal_t {}; struct last_delayed final : public so_5::signal_t {}; struct periodic final : public so_5::signal_t {}; //    . timer_ns::timer_id_t m_first; timer_ns::timer_id_t m_second; timer_ns::timer_id_t m_last; timer_ns::timer_id_t m_periodic; public : example_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self() .event( &example_t::on_first_delayed ) .event( &example_t::on_second_delayed ) .event( &example_t::on_last_delayed ) .event( &example_t::on_periodic ); } void so_evt_start() override { using namespace std::chrono_literals; //      ... m_first = timer_ns::send_delayed< first_delayed >( *this, 100ms ); m_second = timer_ns::send_delayed< second_delayed >( *this, 200ms ); m_last = timer_ns::send_delayed< last_delayed >( *this, 300ms ); // ...    . m_periodic = timer_ns::send_periodic< periodic >( *this, 75ms, 75ms ); //    220ms.       //    first_delaye, second_delayed  //    periodic. std::cout << "hang the agent..." << std::flush; std::this_thread::sleep_for( 220ms ); std::cout << "done" << std::endl; } private : void on_first_delayed( mhood_t<first_delayed> ) { std::cout << "first_delayed received" << std::endl; //   second_delayed  periodic. //          ,  //       . m_second.revoke(); m_periodic.revoke(); } void on_second_delayed( mhood_t<second_delayed> ) { std::cout << "second_delayed received" << std::endl; } void on_last_delayed( mhood_t<last_delayed> ) { std::cout << "last_delayed received" << std::endl; so_deregister_agent_coop_normally(); } void on_periodic( mhood_t<periodic> ) { std::cout << "periodic received" << std::endl; } }; int main() { so_5::launch( [](so_5::environment_t & env) { env.register_agent_as_coop( "example", env.make_agent<example_t>() ); } ); return 0; } 


Apa yang kita punya di sini?

Kami memiliki agen yang pertama kali memulai beberapa pesan penghitung waktu, dan kemudian memblokir utas kerjanya untuk sementara waktu. Selama waktu ini, timer berhasil mengantri agen beberapa permintaan sebagai akibat dari timer yang dipicu: beberapa instance periodik, satu first_delayed dan second_delayed masing-masing.

Dengan demikian, ketika agen membuka utangnya, ia harus menerima periodik pertama dan first_delayed. Saat memproses first_delayed, agen membatalkan pengiriman periodik dan second_delayed. Oleh karena itu, sinyal-sinyal ini seharusnya tidak mencapai agen, terlepas dari apakah mereka sudah dalam antrian agen atau tidak (dan mereka).

Kami melihat hasil dari contoh:

 hang the agent...done periodic received first_delayed received last_delayed received 

Ya, benar. Mendapat periodik pertama dan first_delayed. Maka tidak ada periodik atau second_delayed.

Tetapi jika dalam contoh kita mengganti "timer" dari so_5 :: extra :: revocable_timer dengan timer standar dari SObjectizer, hasilnya akan berbeda: semua instance sinyal periodik dan second_delayed yang sudah memasuki antrian agen akan mencapai agen.

Pesan Terbatas Waktu Pengiriman


Hal lain yang berguna, kadang-kadang, yang akan tersedia di so_5_extra-1.2.0 adalah pengiriman pesan dengan batas waktu. Misalnya, agen request_handler mengirim pesan verifikasi_signature ke agen crypto_master. Pada saat yang sama, request_handler ingin verifikasi_syarat dikirimkan dalam waktu 5 detik. Jika ini tidak terjadi, maka tidak akan ada artinya dalam pemrosesan verity_signature, agen request_handler akan berhenti bekerja.

Dan agen crypto_master adalah kawan yang suka berubah menjadi “bottleneck”: kadang-kadang ia mulai melambat. Pada saat seperti itu, pesan diakumulasikan dalam antrian, seperti tanda verifikasi_ di atas, yang dapat menunggu hingga crypto_master dihapus.

Misalkan request_handler mengirim pesan verifikasi_signature ke agen crypto_master, tetapi kemudian crypto_master macet dan macet selama 10 detik. Agen request_handler sudah "jatuh", mis. sudah mengirim semua orang penolakan layanan dan menyelesaikan pekerjaannya. Tapi pesan verifikasi_masih tetap dalam antrian crypto_master! Jadi, ketika crypto_master "batalkan", ia akan menerima pesan ini dan akan memproses pesan ini. Meskipun ini tidak lagi diperlukan.

Dengan menggunakan amplop baru so_5 :: extra :: enveloped_msg :: time_limited_delivery_t, kami dapat menyelesaikan masalah ini: agen request_handler akan mengirim verifikasi_signature time_limited_delivery_t terlampir dalam amplop dengan batas waktu pengiriman:

 so_5::extra::enveloped_msg::make<verify_signature>(...) .envelope<so_5::extra::enveloped_msg::time_limited_delivery_t>(5s) .send_to(crypto_master_mbox); 

Sekarang jika crypto_master “tetap” dan tidak berhasil memverifikasi_signature dalam 5 detik, amplop tidak akan mengirim pesan ini untuk diproses. Dan crypto_master tidak akan melakukan pekerjaan yang tidak dibutuhkan orang lain.

Laporan pengiriman penerima


Dan akhirnya, contoh hal yang aneh yang tidak diimplementasikan secara teratur baik dalam SObjectizer atau so_5_extra, tetapi yang dapat dilakukan secara mandiri.

Terkadang Anda ingin menerima sesuatu dari SObjectizer seperti pesan "laporan pengiriman" kepada penerima. Bagaimanapun, itu adalah satu hal ketika pesan mencapai penerima, tetapi penerima karena suatu alasan tidak menanggapinya. Hal lain adalah ketika pesan tidak sampai ke penerima sama sekali. Misalnya, itu diblokir oleh mekanisme perlindungan agen yang berlebihan . Dalam kasus pertama, pesan yang kami tidak menunggu jawabannya dapat dihilangkan. Tetapi dalam kasus kedua, mungkin masuk akal untuk mengirim ulang pesan setelah beberapa waktu.

Sekarang kita akan mempertimbangkan bagaimana mekanisme paling sederhana dari "laporan pengiriman" dapat diimplementasikan menggunakan amplop.

Jadi, pertama-tama kita lakukan langkah persiapan yang diperlukan:

 #include <so_5_extra/enveloped_msg/just_envelope.hpp> #include <so_5_extra/enveloped_msg/send_functions.hpp> #include <so_5/all.hpp> using namespace std::chrono_literals; namespace envelope_ns = so_5::extra::enveloped_msg; using request_id_t = int; 

Sekarang kita dapat mendefinisikan pesan yang akan digunakan dalam contoh. Pesan pertama adalah permintaan untuk melakukan beberapa tindakan yang kita butuhkan. Dan pesan kedua adalah konfirmasi bahwa pesan pertama mencapai penerima:

 struct request_t final { request_id_t m_id; std::string m_data; }; struct delivery_receipt_t final { //   request_t::m_id   request_t. request_id_t m_id; }; 

Selanjutnya, kita dapat mendefinisikan agent_tiga agen yang akan memproses pesan bertipe request_t. Tetapi pemrosesan akan dengan meniru "menempel". Yaitu ia memproses request_t, setelah itu ia mengubah statusnya dari st_normal menjadi st_busy. Dalam keadaan st_busy, ia tidak melakukan apa-apa dan mengabaikan semua pesan yang sampai padanya.

Ini berarti bahwa jika agen processor_t mengirim tiga pesan request_t berturut-turut, itu akan memproses yang pertama dan dua lainnya akan dilemparkan, karena saat memproses pesan pertama, agen akan pergi ke st_busy dan mengabaikan apa yang akan datang ketika sedang dalam st_busy.

Dalam st_busy, agen processor_t akan menghabiskan 2 detik, setelah itu ia akan kembali ke st_normal dan akan siap untuk memproses pesan baru.

Inilah yang terlihat seperti agen prosesor:

 class processor_t final : public so_5::agent_t { //   .     //   . state_t st_normal{this, "normal"}; //  " ".   . state_t st_busy{this, "busy"}; public: processor_t(context_t ctx) : so_5::agent_t{std::move(ctx)} { this >>= st_normal; st_normal.event(&processor_t::on_request); //     ,    . //  2   ,    st_normal. st_busy.time_limit(2s, st_normal); } private: void on_request(mhood_t<request_t> cmd) { std::cout << "processor: on_request(" << cmd->m_id << ", " << cmd->m_data << ")" << std::endl; this >>= st_busy; } }; 

Sekarang kita dapat mendefinisikan agen request_generator_t, yang memiliki banyak permintaan yang perlu dikirim ke processor_t. Agen request_generator_t mengirim seluruh paket setiap 3 detik, dan kemudian menunggu konfirmasi pengiriman dalam bentuk delivery_receipt_t.

Ketika delivery_recept_t tiba, agen request_generator_t membuang permintaan yang dikirim keluar dari bundel. Jika paket benar-benar kosong, maka contoh selesai. Jika ada yang lain, maka paket yang tersisa akan dikirim lagi ketika pengiriman berikutnya tiba.

Jadi di sini adalah kode agen request_generator_t. Ini cukup banyak, tetapi primitif. Anda hanya dapat memperhatikan internal metode send_requests (), di mana pesan request_t dikirim, dilampirkan dalam amplop khusus.

Kode agen requests_generator_t
 class requests_generator_t final : public so_5::agent_t { //    . const so_5::mbox_t m_processor; //  ,       . std::map<request_id_t, std::string> m_requests; struct resend_requests final : public so_5::signal_t {}; public: requests_generator_t(context_t ctx, so_5::mbox_t processor) : so_5::agent_t{std::move(ctx)} , m_processor{std::move(processor)} { so_subscribe_self() .event(&requests_generator_t::on_delivery_receipt) .event(&requests_generator_t::on_resend); } void so_evt_start() override { //    . m_requests.emplace(0, "First"); m_requests.emplace(1, "Second"); m_requests.emplace(2, "Third"); m_requests.emplace(3, "Four"); //  . send_requests(); } private: void on_delivery_receipt(mhood_t<delivery_receipt_t> cmd) { std::cout << "request delivered: " << cmd->m_id << std::endl; m_requests.erase(cmd->m_id); if(m_requests.empty()) //    .  . so_deregister_agent_coop_normally(); } void on_resend(mhood_t<resend_requests>) { std::cout << "time to resend requests, pending requests: " << m_requests.size() << std::endl; send_requests(); } void send_requests() { for(const auto & item : m_requests) { std::cout << "sending request: (" << item.first << ", " << item.second << ")" << std::endl; envelope_ns::make<request_t>(item.first, item.second) .envelope<custom_envelope_t>(so_direct_mbox(), item.first) .send_to(m_processor); } //       3 . so_5::send_delayed<resend_requests>(*this, 3s); } }; 

Sekarang kami memiliki pesan dan agen yang harus menggunakan pesan ini untuk berkomunikasi. Hanya ada satu hal kecil yang tersisa - entah bagaimana membuat pesan delivery_receipt_t tiba ketika mengirimkan request_t ke processor_t.

Ini dilakukan dengan menggunakan amplop ini:

 class custom_envelope_t final : public envelope_ns::just_envelope_t { //     . const so_5::mbox_t m_to; // ID  . const request_id_t m_id; public: custom_envelope_t(so_5::message_ref_t payload, so_5::mbox_t to, request_id_t id) : envelope_ns::just_envelope_t{std::move(payload)} , m_to{std::move(to)} , m_id{id} {} void handler_found_hook(handler_invoker_t & invoker) noexcept override { //    ,     . //     . so_5::send<delivery_receipt_t>(m_to, m_id); //      . envelope_ns::just_envelope_t::handler_found_hook(invoker); } }; 

Secara umum, tidak ada yang rumit. Kami mewarisi dari so_5 :: extra :: enveloped_msg :: just_envelope_t. Ini adalah jenis tambahan amplop yang menyimpan pesan yang terlampir di dalamnya dan menyediakan implementasi dasar dari hooks
handler_found_hook () dan transformation_hook (). Oleh karena itu, kita hanya dapat menyimpan atribut yang kita butuhkan di dalam custom_envelope_t dan mengirim delivery_receipt_t di dalam kait handler_found_hook ().

Faktanya, itu saja. Jika kita menjalankan contoh ini, kita mendapatkan yang berikut:

 sending request: (0, First) sending request: (1, Second) sending request: (2, Third) sending request: (3, Four) processor: on_request(0, First) request delivered: 0 time to resend requests, pending requests: 3 sending request: (1, Second) sending request: (2, Third) sending request: (3, Four) processor: on_request(1, Second) request delivered: 1 time to resend requests, pending requests: 2 sending request: (2, Third) sending request: (3, Four) processor: on_request(2, Third) request delivered: 2 time to resend requests, pending requests: 1 sending request: (3, Four) processor: on_request(3, Four) request delivered: 3 

Selain itu, saya harus mengatakan bahwa dalam praktiknya, custom_envelope_t sederhana untuk menghasilkan laporan pengiriman hampir tidak cocok. Tetapi jika seseorang tertarik dengan topik ini, maka dapat dibahas dalam komentar, dan tidak menambah volume artikel.

Apa lagi yang bisa dilakukan dengan amplop?


Pertanyaan bagus!Kami sendiri tidak memiliki jawaban yang komprehensif. Mungkin, kemungkinan hanya dibatasi oleh imajinasi pengguna. Nah, jika untuk realisasi fantasi di SObjectizer ada yang hilang, maka ini bisa diceritakan kepada kita. Kami selalu mendengarkan. Dan, yang penting, kadang-kadang kita lakukan :)

Integrasi agen dengan mchain


Berbicara sedikit lebih serius, itu adalah fitur lain yang ingin saya miliki dari waktu ke waktu dan yang bahkan direncanakan untuk so_5_extra-1.2.0. Tetapi yang, kemungkinan besar, tidak akan jatuh ke rilis 1.2.0.

Ini tentang menyederhanakan integrasi kereta dan agen.

Faktanya adalah bahwa awalnya rantai ditambahkan ke SObjectizer untuk menyederhanakan komunikasi agen dengan bagian lain dari aplikasi yang ditulis tanpa agen. Misalnya, ada utas utama aplikasi, di mana pengguna berinteraksi menggunakan GUI. Dan ada beberapa agen-pekerja yang melakukan pekerjaan "keras" latar belakang. Mengirim pesan ke agen dari utas utama tidak menjadi masalah: panggil saja pengiriman biasa. Tetapi bagaimana cara mentransfer informasi kembali?

Untuk ini, mchain-s ditambahkan.

Namun seiring berjalannya waktu, ternyata kereta bisa memainkan peran yang jauh lebih besar. Mungkin, pada prinsipnya, untuk membuat aplikasi multithreaded pada SObjectizer tanpa agen sama sekali, hanya pada mchain-ahs (lebih detail di sini ). Dan Anda dapat menggunakan mchain-s sebagai sarana untuk menyeimbangkan beban pada agen. Sebagai mekanisme untuk memecahkan masalah produsen-konsumen.

Masalah dengan produsen-konsumen adalah jika produsen menghasilkan pesan lebih cepat daripada yang dapat ditangani konsumen, maka kita dalam masalah. Antrian pesan akan bertambah, kinerja dapat menurun seiring waktu, atau aplikasi akan macet sama sekali karena kehabisan memori.

Solusi biasa yang kami usulkan untuk digunakan dalam kasus ini adalah menggunakansepasang agen penagih kolektor . Anda juga dapat menggunakan batas pesan (baik sebagai mekanisme perlindungan utama, atau sebagai tambahan untuk pengumpul-pemain). Tetapi menulis kolektor-pemain membutuhkan pekerjaan tambahan dari programmer.

Tapi rantai dapat digunakan untuk tujuan ini dengan upaya minimal dari pengembang. Jadi, produser akan memasukkan pesan berikutnya di mchain, dan konsumen akan mengambil pesan dari mchain ini.

Tetapi masalahnya adalah bahwa ketika konsumen adalah agen, itu tidak terlalu nyaman bagi agen untuk bekerja dengan mchain melalui fungsi accept () dan select () yang tersedia. Dan ketidaknyamanan ini dapat dicoba untuk dihilangkan dengan bantuan beberapa alat untuk mengintegrasikan agen dan mchain-s.

Saat mengembangkan alat semacam itu, beberapa masalah perlu dipecahkan. Misalnya, ketika sebuah pesan tiba di mchain, pada titik apa pesan itu harus diekstraksi dari mchain? Jika konsumen bebas dan tidak memproses apa pun, maka Anda dapat mengambil pesan dari mchain segera dan memberikannya kepada agen konsumen. Jika pesan sudah dikirim ke konsumen dari mchain, dia belum berhasil memproses pesan ini, tetapi pesan baru telah tiba di mchain ... Apa yang harus dilakukan dalam kasus ini?

Ada spekulasi bahwa amplop dapat membantu dalam kasus ini. Jadi, ketika kami mengambil pesan pertama dari mchain dan mengirimkannya ke konsumen, kami membungkus pesan ini dalam amplop khusus. Ketika amplop melihat bahwa pesan telah dikirim dan diproses, ia meminta pesan berikutnya dari mchain (jika ada).

Tentu saja, semuanya tidak begitu sederhana di sini. Namun sejauh ini tampilannya cukup solvable. Dan, saya harap, mekanisme serupa akan muncul di salah satu versi so_5_extra berikutnya.

Apakah kita akan membuka kotak Pandora?


Perlu dicatat bahwa dengan kita kemampuan tambahan itu sendiri menyebabkan perasaan ganda.

Di satu sisi, amplop sudah memungkinkan / memungkinkan untuk melakukan hal-hal yang sebelumnya disebutkan (tetapi hanya bermimpi tentang sesuatu). Misalnya, ini adalah pembatalan dijamin timer dan pembatasan waktu pengiriman, laporan pengiriman, kemampuan untuk mengingat pesan yang sebelumnya dikirim.

Di sisi lain, tidak jelas apa yang akan terjadi selanjutnya. Lagipula, Anda dapat membuat masalah dari peluang apa pun jika Anda mulai menggunakan peluang ini di tempat yang Anda butuhkan dan di mana Anda tidak. Jadi mungkin kita membuka kotak Pandora dan kita masih tidak membayangkan apa yang menanti kita?

Tetap bersabar dan melihat ke mana semua ini akan menuntun kita.

Tentang rencana pengembangan langsung SObjectizer alih-alih menyimpulkan


Alih-alih kesimpulan, saya ingin berbicara tentang bagaimana kita melihat masa depan SObjectizer yang sangat dekat (dan tidak hanya). Jika seseorang tidak senang dengan sesuatu dalam rencana kami, maka Anda dapat berbicara dan memengaruhi bagaimana SObjectizer-5 akan berkembang.

Versi beta pertama dari SObjectizer-5.5.23 dan so_5_extra-1.2.0 sudah diperbaiki dan tersedia untuk diunduh dan percobaan. Masih ada banyak pekerjaan yang harus dilakukan di bidang dokumentasi dan kasus penggunaan. Karena itu, rilis resmi direncanakan pada dekade pertama bulan November. Jika berhasil lebih awal, kami akan melakukannya lebih awal.

Pelepasan SObjectizer-5.5.23 tampaknya berarti bahwa evolusi cabang 5.5 akan segera berakhir. Ini adalah rilis pertama dari cabang ini diadakan empat tahun lalu, bulan Oktober 2014. Sejak itu, SObjectizer-5 telah berevolusi dalam cabang 5.5 tanpa ada perubahan besar antar versi. Itu tidak mudah. Terutama mengingat fakta bahwa selama ini kami harus melihat kembali pada kompiler yang jauh dari dukungan ideal untuk C ++ 11.

Sekarang kami tidak melihat alasan untuk melihat kembali kompatibilitas di dalam cabang 5.5, dan terutama pada kompiler C ++ yang lebih lama. Apa yang bisa dibenarkan pada tahun 2014, ketika C ++ 14 baru saja siap untuk secara resmi diadopsi, dan C ++ 17 belum ada di cakrawala, sekarang terlihat sangat berbeda.

Plus, dalam SObjectizer-5.5 sendiri sudah ada akumulasi cukup banyak rake dan backup, yang muncul karena kompatibilitas yang sama dan yang mempersulit pengembangan SObjectizer lebih lanjut.

Oleh karena itu, dalam beberapa bulan mendatang kita akan bertindak sesuai dengan skenario berikut:

1. Pengembangan versi selanjutnya dari so_5_extra, di mana saya ingin menambahkan alat untuk menyederhanakan penulisan tes untuk agen. Apakah itu akan so_5_extra-1.3.0 (mis. Dengan memecah perubahan relatif ke 1.2.0) atau apakah itu akan so_5_extra-1.2.1 (mis. Tanpa memecah perubahan) belum jelas. Mari kita lihat bagaimana kelanjutannya. Jelas bahwa versi so_5_extra berikutnya akan didasarkan pada SObjectizer-5.5.

1a. Jika untuk versi so_5_extra selanjutnya Anda perlu melakukan sesuatu tambahan di SObjectizer-5.5, maka versi berikutnya 5.5.24 akan dirilis. Jika untuk so_5_extra tidak perlu melakukan perbaikan pada kernel SObjectizer, maka versi 5.5.23 akan berubah menjadi versi signifikan terakhir dalam kerangka cabang 5.5. Rilis minor perbaikan bug akan keluar. Tetapi pengembangan cabang 5.5 itu sendiri berhenti pada versi 5.5.23 atau 5.5.24.

2. Kemudian versi SObjectizer-5.6.0 akan dirilis, yang akan membuka cabang baru. Di cabang 5.6, kami akan membersihkan kode SObjectizer dari semua kruk dan cadangan yang terakumulasi, serta dari sampah lama yang telah lama ditandai sebagai usang. Kemungkinan beberapa hal akan mengalami refactoring (misalnya, abstract_message_box_t dapat diubah), tetapi hampir tidak kardinal. Prinsip dasar kerja dan fitur karakteristik SObjectizer-5.5 di SObjectizer-5.6 akan tetap dalam bentuk yang sama.

SObjectizer-5.6 sudah membutuhkan C ++ 14 (setidaknya pada level GCC-5.5). Kompiler Visual C ++ di bawah VC ++ 15 (yang berasal dari Visual Studio 2017) tidak akan didukung.

Kami menganggap cabang 5.6 sebagai cabang stabil dari SObjectizer, yang akan relevan sampai versi pertama SObjectizer-5.7 muncul.

Saya ingin merilis versi 5.6.0 pada awal 2019, tentatif pada bulan Februari.

3. Setelah menstabilkan cabang 5.6, kami ingin mulai bekerja pada cabang 5.7, di mana kami dapat merevisi beberapa prinsip dasar pekerjaan SObjectizer. Misalnya, sepenuhnya meninggalkan operator publik, hanya menyisakan yang pribadi. Mengulangi mekanisme koperasi dan hubungan orangtua-anak mereka, sehingga menghilangkan hambatan selama pendaftaran / deregistrasi koperasi. Hapus pembagian dengan pesan / sinyal. Hanya izinkan send / send_delayed / send_ periodic untuk mengirim pesan, dan sembunyikan metode deliver_message dan schedule_timer “di bawah tenda”. Ubah mekanisme untuk mengirim pesan sehingga menghapus dynamic_casts sepenuhnya dari proses ini, atau kurangi seminimal mungkin.

Secara umum, ada tempat untuk berbalik. Pada saat yang sama, SObjectizer-5.7 sudah membutuhkan C ++ 17, tanpa memperhatikan C ++ 14.

Jika Anda melihat hal-hal tanpa kacamata merah muda, ada baiknya jika rilis 5.7.0 terjadi pada akhir musim gugur 2019. Itu. versi kerja utama dari SObjectizer untuk 2019 akan menjadi cabang 5.6.

4. Sejalan dengan semua ini, so_5_extra akan berkembang. Mungkin, versi so_5_extra-2 akan dirilis bersama dengan SObjectizer-5.6, yang akan menggabungkan fungsionalitas baru selama 2019, tetapi berdasarkan pada SObjectizer-5.6.

Jadi, kita sendiri melihat evolusi progresif untuk SObjectizer-5 dengan revisi bertahap dari beberapa prinsip dasar SObjectizer-5. Pada saat yang sama, kami akan mencoba melakukan ini semulus mungkin sehingga memungkinkan untuk beralih dari satu versi ke versi lain dengan sedikit rasa sakit.

Namun, jika seseorang ingin perubahan yang lebih dramatis dan signifikan dari SObjectizer, maka kami memiliki beberapa pemikiran tentang ini . Singkatnya: Anda dapat membuat ulang SObjectizer sesuka Anda, segera untuk mengimplementasikan SObjectizer-6 untuk bahasa pemrograman lain. Tetapi kami tidak akan melakukan ini sepenuhnya dengan biaya kami sendiri, karena ini terjadi dengan evolusi SObjectizer-5.

Itu mungkin saja.Komentar untuk artikel sebelumnya ternyata merupakan diskusi yang baik dan konstruktif. Akan bermanfaat bagi kita jika diskusi serupa terjadi kali ini. Seperti biasa, kami siap menjawab pertanyaan apa pun, tetapi untuk pertanyaan yang masuk akal, dan dengan senang hati.

Dan kepada pembaca yang paling sabar yang telah mencapai garis ini, banyak terima kasih atas waktu yang dihabiskan untuk membaca artikel.

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


All Articles