]> git.sesse.net Git - casparcg/commitdiff
* implemented lifecycle-bound functions in caspar::io::connection
authorniklaspandersson <niklas.p.andersson@svt.se>
Thu, 8 Aug 2013 15:03:13 +0000 (17:03 +0200)
committerniklaspandersson <niklas.p.andersson@svt.se>
Thu, 8 Aug 2013 15:03:13 +0000 (17:03 +0200)
* refactored caspar::io::connection to avoid circular references
* moved delimiter-chunking logic out from AMCPProtocolStrategy and CIIProtocolStrategy. They now depend on being wrapped by a delimiter_based_chunking_strategy

16 files changed:
common/param.h
protocol/amcp/AMCPProtocolStrategy.cpp
protocol/amcp/AMCPProtocolStrategy.h
protocol/cii/CIIProtocolStrategy.cpp
protocol/cii/CIIProtocolStrategy.h
protocol/protocol.vcxproj
protocol/protocol.vcxproj.filters
protocol/util/AsyncEventServer.cpp
protocol/util/AsyncEventServer.h
protocol/util/ClientInfo.h
protocol/util/ProtocolStrategy.h
protocol/util/protocol_strategy.h
protocol/util/strategy_adapters.cpp
protocol/util/strategy_adapters.h
shell/main.cpp
shell/server.cpp

index 87a08a084ed8c01e3c9ba98289cf7b68f21f4912..ae38d35b164bedc9c01f2b8ec9cb858cd49e3db5 100644 (file)
@@ -27,7 +27,6 @@ template<typename T, typename C>
 typename std::enable_if<!std::is_convertible<T, std::wstring>::value, typename std::decay<T>::type>::type get_param(const std::wstring& name, C&& params, T fail_value = T())
 {      
        auto it = std::find_if(std::begin(params), std::end(params), param_comparer(name));
-       //auto it = std::find(std::begin(params), std::end(params), name);
        if(it == params.end())  
                return fail_value;
        
@@ -48,7 +47,6 @@ template<typename C>
 std::wstring get_param(const std::wstring& name, C&& params, const std::wstring& fail_value = L"")
 {      
        auto it = std::find_if(std::begin(params), std::end(params), param_comparer(name));
-       //auto it = std::find(std::begin(params), std::end(params), name);
        if(it == params.end())  
                return fail_value;
        
index 18251aacbec196d02337e4456e9894fbc4bb4481..cf24372a181fab8e53c6df66df811cb2ad90c44b 100644 (file)
@@ -76,42 +76,10 @@ AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<cor
 AMCPProtocolStrategy::~AMCPProtocolStrategy() {
 }
 
-void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)
+//The paser method expects message to be complete messages with the delimiter stripped away.
+//Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
+void AMCPProtocolStrategy::Parse(const std::wstring& message, ClientInfoPtr pClientInfo)
 {
-       size_t pos;
-       std::wstring recvData(pData, charCount);
-       std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;
-
-       while(true) {
-               pos = availibleData.find(MessageDelimiter);
-               if(pos != std::wstring::npos)
-               {
-                       std::wstring message = availibleData.substr(0,pos);
-
-                       //This is where a complete message gets taken care of
-                       if(message.length() > 0) {
-                               ProcessMessage(message, pClientInfo);
-                       }
-
-                       std::size_t nextStartPos = pos + MessageDelimiter.length();
-                       if(nextStartPos < availibleData.length())
-                               availibleData = availibleData.substr(nextStartPos);
-                       else {
-                               availibleData.clear();
-                               break;
-                       }
-               }
-               else
-               {
-                       break;
-               }
-       }
-       if(pClientInfo)
-               pClientInfo->currentMessage_ = availibleData;
-}
-
-void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)
-{      
        CASPAR_LOG(info) << L"Received message from " << pClientInfo->print() << ": " << message << L"\\r\\n";
        
        bool bError = true;
index 5d820f81a2f1cc24c365c34b43e5e347b9475d73..524314d4e5e05b02b16519e600000a78bf41ef6d 100644 (file)
@@ -51,17 +51,13 @@ public:
        AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels);
        virtual ~AMCPProtocolStrategy();
 
-       virtual void Parse(const TCHAR* pData, int charCount, IO::ClientInfoPtr pClientInfo);
-       virtual std::string GetCodepage() {
-               return "UTF-8";
-       }
-
+       virtual void Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo);
+       virtual std::string GetCodepage() {     return "UTF-8"; }
        AMCPCommand::ptr_type InterpretCommandString(const std::wstring& str, MessageParserState* pOutState=0);
 
 private:
        friend class AMCPCommand;
 
-       void ProcessMessage(const std::wstring& message, IO::ClientInfoPtr& pClientInfo);
        std::size_t TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector);
        AMCPCommand::ptr_type CommandFactory(const std::wstring& str);
 
index 6ed2cef98413ee89defdce14f7debc90e22e9909..551cf30665a16e6e51a16517b535abf7f62370c8 100644 (file)
@@ -49,38 +49,16 @@ CIIProtocolStrategy::CIIProtocolStrategy(const std::vector<spl::shared_ptr<core:
 {
 }
 
-void CIIProtocolStrategy::Parse(const TCHAR* pData, int charCount, IO::ClientInfoPtr pClientInfo) 
+//The paser method expects message to be complete messages with the delimiter stripped away.
+//Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
+void CIIProtocolStrategy::Parse(const std::wstring& message, IO::ClientInfoPtr pClientInfo) 
 {
-       std::size_t pos;
-       std::wstring msg(pData, charCount);
-       std::wstring availibleData = currentMessage_ + msg;
-
-       while(true)
+       if(message.length() > 0)
        {
-               pos = availibleData.find(MessageDelimiter);
-               if(pos != std::wstring::npos)
-               {
-                       std::wstring message = availibleData.substr(0,pos);
-
-                       if(message.length() > 0) {
-                               ProcessMessage(message, pClientInfo);
-                               if(pClientInfo != 0)
-                                       pClientInfo->Send(TEXT("*\r\n"));
-                       }
-
-                       std::size_t nextStartPos = pos + MessageDelimiter.length();
-                       if(nextStartPos < availibleData.length())
-                               availibleData = availibleData.substr(nextStartPos);
-                       else 
-                       {
-                               availibleData.clear();
-                               break;
-                       }
-               }
-               else
-                       break;
+               ProcessMessage(message, pClientInfo);
+               if(pClientInfo != 0)
+                       pClientInfo->Send(TEXT("*\r\n"));
        }
-       currentMessage_ = availibleData;
 }
 
 void CIIProtocolStrategy::ProcessMessage(const std::wstring& message, IO::ClientInfoPtr pClientInfo)
index 032e2add86297e2804987f5d6f5323dfdb5f1829..04df30b77e2188c89b19a66661de7e5d912050ae 100644 (file)
@@ -40,7 +40,7 @@ class CIIProtocolStrategy : public IO::IProtocolStrategy
 public:
        CIIProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels);
 
-       void Parse(const TCHAR* pData, int charCount, IO::ClientInfoPtr pClientInfo);
+       void Parse(const std::wstring& message, IO::ClientInfoPtr pClientInfo);
        std::string GetCodepage() {return "ISO-8859-1";}        //ISO 8859-1
 
        void SetProfile(const std::wstring& profile) {currentProfile_ = profile;}
index b48b49c7502d83bc75b1c07580a11653de35fdc3..7d8e12a48da867dd86b98cf03fe38c6ae1f9d16b 100644 (file)
@@ -50,6 +50,7 @@
     <ClInclude Include="StdAfx.h" />\r
     <ClInclude Include="util\AsyncEventServer.h" />\r
     <ClInclude Include="util\ClientInfo.h" />\r
+    <ClInclude Include="util\lock_container.h" />\r
     <ClInclude Include="util\ProtocolStrategy.h" />\r
     <ClInclude Include="util\protocol_strategy.h" />\r
     <ClInclude Include="util\strategy_adapters.h" />\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../StdAfx.h</PrecompiledHeaderFile>\r
     </ClCompile>\r
+    <ClCompile Include="util\lock_container.cpp">\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">..\StdAfx.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\StdAfx.h</PrecompiledHeaderFile>\r
+    </ClCompile>\r
     <ClCompile Include="util\strategy_adapters.cpp">\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../StdAfx.h</PrecompiledHeaderFile>\r
index 21f469fa208639f86da5968afaecb6cea03d673f..168393d613a97adb451f71b146d6022b29237cf6 100644 (file)
@@ -97,6 +97,9 @@
     <ClInclude Include="util\strategy_adapters.h">\r
       <Filter>source\util</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="util\lock_container.h">\r
+      <Filter>source\util</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClCompile Include="amcp\AMCPCommandQueue.cpp">\r
     <ClCompile Include="util\strategy_adapters.cpp">\r
       <Filter>source\util</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="util\lock_container.cpp">\r
+      <Filter>source\util</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
 </Project>
\ No newline at end of file
index 2552d78696da343f0e78889c04b69a3e613fad0c..82693a70ccd83278a1a0310a8867ff0db930e716 100644 (file)
@@ -33,6 +33,8 @@
 #include <boost/thread.hpp>
 #include <boost/lexical_cast.hpp>
 
+#include <tbb\mutex.h>
+
 using boost::asio::ip::tcp;
 
 namespace caspar { namespace IO {
@@ -41,8 +43,8 @@ class connection;
 
 typedef std::set<spl::shared_ptr<connection>> connection_set;
 
-class connection : public spl::enable_shared_from_this<connection>, public client_connection<char>
-{    
+class connection : public spl::enable_shared_from_this<connection>
+{   
     const spl::shared_ptr<tcp::socket>                 socket_; 
        const spl::shared_ptr<connection_set>           connection_set_;
        const std::wstring                                                      name_;
@@ -50,6 +52,37 @@ class connection : public spl::enable_shared_from_this<connection>, public clien
        std::shared_ptr<protocol_strategy<char>>        protocol_;
 
        std::array<char, 32768>                                         data_;
+       std::vector<std::shared_ptr<void>>                      lifecycle_bound_items_;
+
+       class connection_holder : public client_connection<char>
+       {
+               std::weak_ptr<connection> connection_;
+       public:
+               explicit connection_holder(std::weak_ptr<connection> conn) : connection_(conn)
+               {}
+
+               virtual void send(std::basic_string<char>&& data)
+               {
+                       auto conn = connection_.lock();
+                       conn->send(std::move(data));
+               }
+               virtual void disconnect()
+               {
+                       auto conn = connection_.lock();
+                       conn->disconnect();
+               }
+               virtual std::wstring print() const
+               {
+                       auto conn = connection_.lock();
+                       return conn->print();
+               }
+
+               virtual void bind_to_lifecycle(const std::shared_ptr<void>& lifecycle_bound)
+               {
+                       auto conn = connection_.lock();
+                       return conn->bind_to_lifecycle(lifecycle_bound);
+               }
+       };
 
 public:
     static spl::shared_ptr<connection> create(spl::shared_ptr<tcp::socket> socket, const protocol_strategy_factory<char>::ptr& protocol, spl::shared_ptr<connection_set> connection_set)
@@ -59,10 +92,20 @@ public:
                return con;
     }
 
+       ~connection()
+       {
+               CASPAR_LOG(info) << print() << L" connection destroyed.";
+       }
+
        std::wstring print() const
        {
                return L"[" + name_ + L"]";
        }
+
+       const std::string ipv4_address() const
+       {
+               return socket_->is_open() ? socket_->local_endpoint().address().to_string() : "no-address";
+       }
        
        /* ClientInfo */
        
@@ -75,7 +118,11 @@ public:
        {
                stop();
        }
-       
+       void bind_to_lifecycle(const std::shared_ptr<void>& lifecycle_bound)
+       {
+               lifecycle_bound_items_.push_back(lifecycle_bound);
+       }
+
        /**************/
        
        void stop()
@@ -89,15 +136,16 @@ public:
                {
                        CASPAR_LOG_CURRENT_EXCEPTION();
                }
+               
                CASPAR_LOG(info) << print() << L" Disconnected.";
        }
 
 private:
-    connection(const spl::shared_ptr<tcp::socket>& socket, const protocol_strategy_factory<char>::ptr& protocol, const spl::shared_ptr<connection_set>& connection_set) 
+    connection(const spl::shared_ptr<tcp::socket>& socket, const protocol_strategy_factory<char>::ptr& protocol_factory, const spl::shared_ptr<connection_set>& connection_set) 
                : socket_(socket)
                , name_((socket_->is_open() ? u16(socket_->local_endpoint().address().to_string() + ":" + boost::lexical_cast<std::string>(socket_->local_endpoint().port())) : L"no-address"))
                , connection_set_(connection_set)
-               , protocol_factory_(protocol)
+               , protocol_factory_(protocol_factory)
        {
                CASPAR_LOG(info) << print() << L" Connected.";
     }
@@ -105,7 +153,7 @@ private:
        protocol_strategy<char>& protocol()
        {
                if (!protocol_)
-                       protocol_ = protocol_factory_->create(shared_from_this());
+                       protocol_ = protocol_factory_->create(spl::make_shared<connection_holder>(shared_from_this()));
 
                return *protocol_;
        }
@@ -157,13 +205,15 @@ struct AsyncEventServer::implementation
 {
        boost::asio::io_service                                 service_;
        tcp::acceptor                                                   acceptor_;
-       protocol_strategy_factory<char>::ptr    protocol_;
+       protocol_strategy_factory<char>::ptr    protocol_factory_;
        spl::shared_ptr<connection_set>                 connection_set_;
        boost::thread                                                   thread_;
+       std::vector<lifecycle_factory_t>                lifecycle_factories_;
+       tbb::mutex mutex_;
 
        implementation(const protocol_strategy_factory<char>::ptr& protocol, unsigned short port)
                : acceptor_(service_, tcp::endpoint(tcp::v4(), port))
-               , protocol_(protocol)
+               , protocol_factory_(protocol)
                , thread_(std::bind(&boost::asio::io_service::run, &service_))
        {
                start_accept();
@@ -201,21 +251,36 @@ struct AsyncEventServer::implementation
                if (!acceptor_.is_open())
                        return;
                
-        if (!error)            
-                       connection_set_->insert(connection::create(socket, protocol_, connection_set_));
+        if (!error)
+               {
+                       auto conn = connection::create(socket, protocol_factory_, connection_set_);
+                       connection_set_->insert(conn);
 
+                       {
+                               tbb::mutex::scoped_lock lock(mutex_);
+
+                               BOOST_FOREACH(auto& lifecycle_factory, lifecycle_factories_)
+                               {
+                                       auto lifecycle_bound = lifecycle_factory(conn->ipv4_address());
+                                       conn->bind_to_lifecycle(lifecycle_bound);
+                               }
+                       }
+               }
                start_accept();
     }
+
+       void add_client_lifecycle_event_factory(const lifecycle_factory_t& factory)
+       {
+               tbb::mutex::scoped_lock lock(mutex_);
+               lifecycle_factories_.push_back(factory);
+       }
 };
 
 AsyncEventServer::AsyncEventServer(
                const protocol_strategy_factory<char>::ptr& protocol, unsigned short port)
-       : impl_(new implementation(protocol, port))
-{
-}
+       : impl_(new implementation(protocol, port)) {}
 
-AsyncEventServer::~AsyncEventServer()
-{
-}
+AsyncEventServer::~AsyncEventServer() {}
+void AsyncEventServer::add_client_lifecycle_event_factory(const lifecycle_factory_t& factory) { impl_->add_client_lifecycle_event_factory(factory); }
 
 }}
\ No newline at end of file
index ace053a6a8af88bfc548310e8d2ebf8947f8c891..85f28cc4405868402fa41287933a416ba982da72 100644 (file)
 
 namespace caspar { namespace IO {
 
+       typedef std::function<std::shared_ptr<void> (const std::string& ipv4_address)>
+               lifecycle_factory_t;
+
 class AsyncEventServer
 {
 public:
        explicit AsyncEventServer(const protocol_strategy_factory<char>::ptr& protocol, unsigned short port);
        ~AsyncEventServer();
+
+       void add_client_lifecycle_event_factory(const lifecycle_factory_t& lifecycle_factory);
+
 private:
        struct implementation;
        std::unique_ptr<implementation> impl_;
index f28a9cc71523cbbe7dfafc48d1f7bef9770ecc1a..6d40f601e20b70117d5e3bfe097190b63e683d0e 100644 (file)
@@ -40,9 +40,9 @@ public:
        virtual void Send(const std::wstring& data) = 0;
        virtual void Disconnect() = 0;
        virtual std::wstring print() const = 0;
-
-       std::wstring            currentMessage_;
+       virtual void bind_to_lifecycle(const std::shared_ptr<void>& lifecycle_bound) = 0;
 };
+
 typedef std::shared_ptr<ClientInfo> ClientInfoPtr;
 
 struct ConsoleClientInfo : public caspar::IO::ClientInfo 
@@ -53,6 +53,7 @@ struct ConsoleClientInfo : public caspar::IO::ClientInfo
        }
        void Disconnect(){}
        virtual std::wstring print() const {return L"Console";}
+       virtual void bind_to_lifecycle(const std::shared_ptr<void>& lifecycle_bound) {}
 };
 
 }}
index 73bc2990cf16c859c3fe73f291777139a405d894..adbac7db24088074cd6410b8d3ea144739019f67 100644 (file)
@@ -31,8 +31,10 @@ class IProtocolStrategy
 public:
        virtual ~IProtocolStrategy(){}
 
-       virtual void Parse(const wchar_t* pData, int charCount, ClientInfoPtr pClientInfo) = 0;
+       virtual void Parse(const std::wstring& msg, ClientInfoPtr pClientInfo) = 0;
        virtual std::string GetCodepage() = 0;
+
+       virtual void on_client_disconnect(IO::ClientInfoPtr pClientInfo) {}
 };
 typedef std::shared_ptr<IProtocolStrategy> ProtocolStrategyPtr;
 
index e4c016d33330ac40a5f7eceff6c6fa924b881069..357d851daec54dfe844d725e5416a2de353b8087 100644 (file)
@@ -65,6 +65,8 @@ public:
        virtual void send(std::basic_string<CharT>&& data) = 0;\r
        virtual void disconnect() = 0;\r
        virtual std::wstring print() const = 0;\r
+\r
+       virtual void bind_to_lifecycle(const std::shared_ptr<void>& lifecycle_bound) = 0;\r
 };\r
 \r
 /**\r
index c4ded52cdf64034f730d4264670804b93c916751..1aa20c918167e85711024b4930a1a19cdf89f88f 100644 (file)
@@ -58,6 +58,11 @@ public:
                : client_(client)\r
                , codepage_(codepage)\r
        {\r
+               CASPAR_LOG(info) << "from_unicode_client_connection created.";\r
+       }\r
+       ~from_unicode_client_connection()\r
+       {\r
+               CASPAR_LOG(info) << "from_unicode_client_connection destroyed.";\r
        }\r
 \r
        virtual void send(std::basic_string<wchar_t>&& data)\r
@@ -76,6 +81,11 @@ public:
        {\r
                return client_->print();\r
        }\r
+\r
+       virtual void bind_to_lifecycle(const std::shared_ptr<void>& lifecycle_bound)\r
+       {\r
+               client_->bind_to_lifecycle(lifecycle_bound);\r
+       }\r
 };\r
 \r
 to_unicode_adapter_factory::to_unicode_adapter_factory(\r
@@ -101,8 +111,15 @@ public:
        legacy_client_info(const client_connection<wchar_t>::ptr& client_connection)\r
                : client_connection_(client_connection)\r
        {\r
+               CASPAR_LOG(info) << "legacy_client_info created.";\r
+       }\r
+\r
+       ~legacy_client_info()\r
+       {\r
+               CASPAR_LOG(info) << "legacy_client_info destroyed.";\r
        }\r
 \r
+\r
        virtual void Disconnect()\r
        {\r
                client_connection_->disconnect();\r
@@ -117,6 +134,11 @@ public:
        {\r
                return client_connection_->print();\r
        }\r
+\r
+       virtual void bind_to_lifecycle(const std::shared_ptr<void>& lifecycle_bound)\r
+       {\r
+               client_connection_->bind_to_lifecycle(lifecycle_bound);\r
+       }\r
 };\r
 \r
 class legacy_strategy_adapter : public protocol_strategy<wchar_t>\r
@@ -130,12 +152,16 @@ public:
                : strategy_(strategy)\r
                , client_info_(std::make_shared<legacy_client_info>(client_connection))\r
        {\r
+               CASPAR_LOG(info) << "legacy_strategy_adapter created.";\r
+       }\r
+       ~legacy_strategy_adapter()\r
+       {\r
+               CASPAR_LOG(info) << "legacy_strategy_adapter destroyed.";\r
        }\r
 \r
        virtual void parse(const std::basic_string<wchar_t>& data)\r
        {\r
-               auto p = data.c_str();\r
-               strategy_->Parse(p, static_cast<int>(data.length()), client_info_);\r
+               strategy_->Parse(data, client_info_);\r
        }\r
 };\r
 \r
index 463d4c171775b5c40542a64085b9bfc6ba604752..02cb721c24b32c322ac0bcb5ed204be71c1fb95f 100644 (file)
@@ -76,7 +76,7 @@ public:
                input_ += data;\r
 \r
                std::vector<std::basic_string<CharT>> split;\r
-               boost::iter_split(split, input_, boost::algorithm::first_finder(delimiter_));\r
+               boost::iter_split(split, input_, boost::algorithm::first_finder(delimiter_));   //TODO: check if this splits on all instances of delimiter_ in the input_\r
 \r
                input_ = std::move(split.back());\r
                split.pop_back();\r
@@ -84,7 +84,7 @@ public:
                BOOST_FOREACH(auto cmd, split)\r
                {\r
                        // TODO: perhaps it would be better to not append the delimiter.\r
-                       strategy_->parse(cmd + delimiter_);\r
+                       strategy_->parse(cmd);\r
                }\r
        }\r
 };\r
index 1c0ede785ce1d1871698bdbe7a39feeaebb55b1d..fad67a490c81603b095acb3aad738c1903c06828 100644 (file)
@@ -284,7 +284,7 @@ void run()
                }
 
                wcmd += L"\r\n";
-               amcp.Parse(wcmd.c_str(), static_cast<int>(wcmd.length()), console_client);
+               amcp.Parse(wcmd, console_client);
        }       
        CASPAR_LOG(info) << "Successfully shutdown CasparCG Server.";
 }
index 1f3a5a783e28fdfc419fdf3670dc78e24c8ee321..8a33080e7ce423ead16184c1aa3b066523341d0e 100644 (file)
@@ -186,6 +186,12 @@ struct server::impl : boost::noncopyable
                                        unsigned int port = xml_controller.second.get(L"port", 5250);
                                        auto asyncbootstrapper = spl::make_shared<IO::AsyncEventServer>(create_protocol(protocol), port);
                                        async_servers_.push_back(asyncbootstrapper);
+
+                                       //TODO: remove - test
+                                       asyncbootstrapper->add_client_lifecycle_event_factory([=] (const std::string& ipv4_address) {
+                                                                                                                                                                       return std::shared_ptr<void>(nullptr, [] (void*) 
+                                                                                                                                                                       { CASPAR_LOG(info) << "Client disconnect (lifecycle)"; });
+                                                                                                                                                               });
                                }
                                else
                                        CASPAR_LOG(warning) << "Invalid controller: " << name;