1616#include < mimalloc.h>
1717#include < signal.h>
1818
19+ #include < regex>
20+
1921#include " base/init.h"
2022#include " base/proc_util.h" // for GetKernelVersion
2123#include " facade/dragonfly_listener.h"
2830#include " strings/human_readable.h"
2931#include " util/accept_server.h"
3032#include " util/epoll/epoll_pool.h"
33+ #include " util/http/http_client.h"
3134#include " util/uring/uring_pool.h"
3235#include " util/varz.h"
3336
@@ -85,6 +88,9 @@ ABSL_FLAG(MaxMemoryFlag, maxmemory, MaxMemoryFlag(0),
8588 "0 - means the program will automatically determine its maximum memory usage. "
8689 "default: 0");
8790
91+ ABSL_FLAG (bool , version_check, true ,
92+ " If true, Will monitor for new releases on Dragonfly servers once a day." );
93+
8894using namespace util ;
8995using namespace facade ;
9096using namespace io ;
@@ -96,6 +102,117 @@ namespace dfly {
96102
97103namespace {
98104
105+ using util::http::TlsClient;
106+
107+ const std::string_view kInvalidVersion {" unknown version" };
108+
109+ std::string GetVersionString (const std::string& from) {
110+ // The server sends a message such as {"latest": "0.12.0"}
111+ const auto reg_match_expr = R"( \{\"latest"\:[ \t]*\"([0-9]+\.[0-9]+\.[0-9]+)\"\})" ;
112+ VLOG (1 ) << " checking version '" << from << " '" ;
113+ auto const regex = std::regex (reg_match_expr);
114+ std::smatch match;
115+ if (std::regex_match (from, match, regex) && match.size () > 1 ) {
116+ // the second entry is the match to the group that holds the version string
117+ return match[1 ].str ();
118+ } else {
119+ return std::string{kInvalidVersion };
120+ }
121+ }
122+
123+ std::string GetRemoteVersion (ProactorBase* proactor, SSL_CTX* ssl_context, const std::string host,
124+ std::string_view service, const std::string& resource,
125+ const std::string& ver_header) {
126+ namespace bh = boost::beast::http;
127+ using ResponseType = bh::response<bh::string_body>;
128+
129+ bh::request<bh::string_body> req{bh::verb::get, resource, 11 /* http 1.1*/ };
130+ req.set (bh::field::host, host);
131+ req.set (bh::field::user_agent, ver_header);
132+ ResponseType res;
133+ TlsClient http_client{proactor};
134+ http_client.set_connect_timeout_ms (2000 );
135+
136+ auto ec = http_client.Connect (host, service, ssl_context);
137+ if (!ec) {
138+ ec = http_client.Send (req, &res);
139+ if (!ec) {
140+ VLOG (1 ) << " successfully got response from HTTP GET for host " << host << " :" << service
141+ << " /" << resource << " response code is " << res.result ();
142+
143+ if (res.result () == bh::status::ok) {
144+ return GetVersionString (res.body ());
145+ }
146+ } else {
147+ VLOG (1 ) << " failed to process send HTTP GET to " << host << " @" << service << " at resource '"
148+ << resource << " ', error: " << ec.message ();
149+ }
150+ } else {
151+ VLOG (1 ) << " failed to connect: " << ec.message ();
152+ }
153+ return std::string{kInvalidVersion };
154+ }
155+
156+ struct VersionMonitor {
157+ fibers_ext::Fiber version_fiber_;
158+ fibers_ext::Done monitor_ver_done_;
159+
160+ void Run (ProactorPool* proactor_pool) {
161+ if (GetFlag (FLAGS_version_check)) {
162+ version_fiber_ = proactor_pool->GetNextProactor ()->LaunchFiber ([this ] { RunTask (); });
163+ }
164+ }
165+
166+ void Shutdown () {
167+ monitor_ver_done_.Notify ();
168+ if (version_fiber_.IsJoinable ()) {
169+ version_fiber_.Join ();
170+ }
171+ }
172+
173+ private:
174+ void RunTask ();
175+ };
176+
177+ void VersionMonitor::RunTask () {
178+ const auto loop_sleep_time = std::chrono::hours (24 ); // every 24 hours
179+
180+ const std::string host_name = " version.dragonflydb.io" ;
181+ const std::string_view port = " 443" ;
182+ const std::string resource = " /v1" ;
183+ const std::string dev_version_name = " dev" ;
184+ const std::string version_header = std::string (" DragonflyDB/" ) + kGitTag ;
185+
186+ // Don't run in dev environment - i.e. where we don't build with
187+ // real version number
188+ if (kGitTag == dev_version_name) {
189+ return ;
190+ }
191+
192+ SSL_CTX* context = TlsClient::CreateSslContext ();
193+ if (!context) {
194+ VLOG (1 ) << " failed to create SSL context - cannot run version monitoring" ;
195+ return ;
196+ }
197+
198+ ProactorBase* my_pb = ProactorBase::me ();
199+ while (true ) {
200+ const std::string remote_version =
201+ GetRemoteVersion (my_pb, context, host_name, port, resource, version_header);
202+
203+ if (remote_version != kGitTag ) {
204+ LOG_FIRST_N (INFO, 1 ) << " Your current version '" << kGitTag
205+ << " ' is not the latest version. A newer version '" << remote_version
206+ << " ' is now available. Please consider an update." ;
207+ }
208+ if (monitor_ver_done_.WaitFor (loop_sleep_time)) {
209+ TlsClient::FreeContext (context);
210+ VLOG (1 ) << " finish running version monitor task" ;
211+ return ;
212+ }
213+ }
214+ }
215+
99216enum class TermColor { kDefault , kRed , kGreen , kYellow };
100217// Returns the ANSI color code for the given color. TermColor::kDefault is
101218// an invalid input.
@@ -195,9 +312,12 @@ bool RunEngine(ProactorPool* pool, AcceptServer* acceptor) {
195312 acceptor->AddListener (mc_port, new Listener{Protocol::MEMCACHE, &service});
196313 }
197314
315+ VersionMonitor version_monitor;
316+
198317 acceptor->Run ();
318+ version_monitor.Run (pool);
199319 acceptor->Wait ();
200-
320+ version_monitor. Shutdown ();
201321 service.Shutdown ();
202322
203323 if (unlink_uds) {
0 commit comments