From 4fadd968fa12130524c8380f33fcfe25d4de79e5 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Wed, 13 Sep 2017 11:24:46 +0900 Subject: Imported Upstream version 1.65.0 Change-Id: Icf8400b375482cb11bcf77440a6934ba360d6ba4 Signed-off-by: DongHun Kwak --- boost/asio/detail/config.hpp | 21 +++++++++--- boost/asio/detail/dev_poll_reactor.hpp | 12 +++++-- boost/asio/detail/epoll_reactor.hpp | 13 ++++++-- boost/asio/detail/impl/dev_poll_reactor.ipp | 5 +++ boost/asio/detail/impl/epoll_reactor.ipp | 39 +++++++++++++++++++--- boost/asio/detail/impl/kqueue_reactor.ipp | 28 ++++++++++++++-- .../detail/impl/reactive_descriptor_service.ipp | 19 ++++++++--- .../detail/impl/reactive_socket_service_base.ipp | 14 ++++++-- boost/asio/detail/impl/select_reactor.ipp | 5 +++ boost/asio/detail/impl/signal_set_service.ipp | 6 ++-- .../detail/impl/win_iocp_socket_service_base.ipp | 19 ++++++++--- boost/asio/detail/kqueue_reactor.hpp | 12 +++++-- boost/asio/detail/op_queue.hpp | 6 ++++ boost/asio/detail/select_reactor.hpp | 13 ++++++-- boost/asio/detail/socket_types.hpp | 6 +++- boost/asio/detail/winapi_thread.hpp | 6 ++-- 16 files changed, 184 insertions(+), 40 deletions(-) (limited to 'boost/asio/detail') diff --git a/boost/asio/detail/config.hpp b/boost/asio/detail/config.hpp index baeb86d78e..afe400e5b7 100644 --- a/boost/asio/detail/config.hpp +++ b/boost/asio/detail/config.hpp @@ -65,7 +65,8 @@ #if !defined(BOOST_ASIO_MSVC) # if defined(BOOST_ASIO_HAS_BOOST_CONFIG) && defined(BOOST_MSVC) # define BOOST_ASIO_MSVC BOOST_MSVC -# elif defined(_MSC_VER) && !defined(__MWERKS__) && !defined(__EDG_VERSION__) +# elif defined(_MSC_VER) && (defined(__INTELLISENSE__) \ + || (!defined(__MWERKS__) && !defined(__EDG_VERSION__))) # define BOOST_ASIO_MSVC _MSC_VER # endif // defined(BOOST_ASIO_HAS_BOOST_CONFIG) && defined(BOOST_MSVC) #endif // defined(BOOST_ASIO_MSVC) @@ -154,6 +155,11 @@ # endif // defined(__GXX_EXPERIMENTAL_CXX0X__) # endif // ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 4) # endif // defined(__GNUC__) +# if defined(BOOST_ASIO_MSVC) +# if (_MSC_VER >= 1900) +# define BOOST_ASIO_HAS_VARIADIC_TEMPLATES 1 +# endif // (_MSC_VER >= 1900) +# endif // defined(BOOST_ASIO_MSVC) # endif // !defined(BOOST_ASIO_DISABLE_VARIADIC_TEMPLATES) #endif // !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES) @@ -172,6 +178,11 @@ # endif // defined(__GXX_EXPERIMENTAL_CXX0X__) # endif // ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 6)) || (__GNUC__ > 4) # endif // defined(__GNUC__) +# if defined(BOOST_ASIO_MSVC) +# if (_MSC_VER >= 1900) +# define BOOST_ASIO_HAS_CONSTEXPR 1 +# endif // (_MSC_VER >= 1900) +# endif // defined(BOOST_ASIO_MSVC) # endif // !defined(BOOST_ASIO_DISABLE_CONSTEXPR) #endif // !defined(BOOST_ASIO_HAS_CONSTEXPR) #if !defined(BOOST_ASIO_CONSTEXPR) @@ -296,11 +307,11 @@ # endif // (__cplusplus >= 201103) # endif // defined(__clang__) # if defined(__GNUC__) -# if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5)) || (__GNUC__ > 4) +# if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) || (__GNUC__ > 4) # if defined(__GXX_EXPERIMENTAL_CXX0X__) # define BOOST_ASIO_HAS_STD_ATOMIC 1 # endif // defined(__GXX_EXPERIMENTAL_CXX0X__) -# endif // ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5)) || (__GNUC__ > 4) +# endif // ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) || (__GNUC__ > 4) # endif // defined(__GNUC__) # if defined(BOOST_ASIO_MSVC) # if (_MSC_VER >= 1700) @@ -988,12 +999,14 @@ # if defined(__linux__) # if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) # if ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3) -# if !defined(__INTEL_COMPILER) && !defined(__ICL) +# if !defined(__INTEL_COMPILER) && !defined(__ICL) \ + && !(defined(__clang__) && defined(__ANDROID__)) # define BOOST_ASIO_HAS_THREAD_KEYWORD_EXTENSION 1 # define BOOST_ASIO_THREAD_KEYWORD __thread # elif defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1100) # define BOOST_ASIO_HAS_THREAD_KEYWORD_EXTENSION 1 # endif // defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1100) + // && !(defined(__clang__) && defined(__ANDROID__)) # endif // ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3) # endif // defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) # endif // defined(__linux__) diff --git a/boost/asio/detail/dev_poll_reactor.hpp b/boost/asio/detail/dev_poll_reactor.hpp index db6f3d1b8f..17f57d5221 100644 --- a/boost/asio/detail/dev_poll_reactor.hpp +++ b/boost/asio/detail/dev_poll_reactor.hpp @@ -102,15 +102,21 @@ public: BOOST_ASIO_DECL void cancel_ops(socket_type descriptor, per_descriptor_data&); // Cancel any operations that are running against the descriptor and remove - // its registration from the reactor. + // its registration from the reactor. The reactor resources associated with + // the descriptor must be released by calling cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_descriptor(socket_type descriptor, per_descriptor_data&, bool closing); - // Cancel any operations that are running against the descriptor and remove - // its registration from the reactor. + // Remove the descriptor's registration from the reactor. The reactor + // resources associated with the descriptor must be released by calling + // cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_internal_descriptor( socket_type descriptor, per_descriptor_data&); + // Perform any post-deregistration cleanup tasks associated with the + // descriptor data. + BOOST_ASIO_DECL void cleanup_descriptor_data(per_descriptor_data&); + // Add a new timer queue to the reactor. template void add_timer_queue(timer_queue& queue); diff --git a/boost/asio/detail/epoll_reactor.hpp b/boost/asio/detail/epoll_reactor.hpp index 64c5b2704c..34ed0370f5 100644 --- a/boost/asio/detail/epoll_reactor.hpp +++ b/boost/asio/detail/epoll_reactor.hpp @@ -63,6 +63,7 @@ public: BOOST_ASIO_DECL descriptor_state(); void set_ready_events(uint32_t events) { task_result_ = events; } + void add_ready_events(uint32_t events) { task_result_ |= events; } BOOST_ASIO_DECL operation* perform_io(uint32_t events); BOOST_ASIO_DECL static void do_complete( io_service_impl* owner, operation* base, @@ -123,14 +124,22 @@ public: per_descriptor_data& descriptor_data); // Cancel any operations that are running against the descriptor and remove - // its registration from the reactor. + // its registration from the reactor. The reactor resources associated with + // the descriptor must be released by calling cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_descriptor(socket_type descriptor, per_descriptor_data& descriptor_data, bool closing); - // Remote the descriptor's registration from the reactor. + // Remove the descriptor's registration from the reactor. The reactor + // resources associated with the descriptor must be released by calling + // cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_internal_descriptor( socket_type descriptor, per_descriptor_data& descriptor_data); + // Perform any post-deregistration cleanup tasks associated with the + // descriptor data. + BOOST_ASIO_DECL void cleanup_descriptor_data( + per_descriptor_data& descriptor_data); + // Add a new timer queue to the reactor. template void add_timer_queue(timer_queue& timer_queue); diff --git a/boost/asio/detail/impl/dev_poll_reactor.ipp b/boost/asio/detail/impl/dev_poll_reactor.ipp index efe2ba7086..7efb05ed02 100644 --- a/boost/asio/detail/impl/dev_poll_reactor.ipp +++ b/boost/asio/detail/impl/dev_poll_reactor.ipp @@ -235,6 +235,11 @@ void dev_poll_reactor::deregister_internal_descriptor( op_queue_[i].cancel_operations(descriptor, ops, ec); } +void dev_poll_reactor::cleanup_descriptor_data( + dev_poll_reactor::per_descriptor_data&) +{ +} + void dev_poll_reactor::run(bool block, op_queue& ops) { boost::asio::detail::mutex::scoped_lock lock(mutex_); diff --git a/boost/asio/detail/impl/epoll_reactor.ipp b/boost/asio/detail/impl/epoll_reactor.ipp index 610ce31ce5..3d3d244e0a 100644 --- a/boost/asio/detail/impl/epoll_reactor.ipp +++ b/boost/asio/detail/impl/epoll_reactor.ipp @@ -359,10 +359,16 @@ void epoll_reactor::deregister_descriptor(socket_type descriptor, descriptor_lock.unlock(); - free_descriptor_state(descriptor_data); - descriptor_data = 0; - io_service_.post_deferred_completions(ops); + + // Leave descriptor_data set so that it will be freed by the subsequent + // call to cleanup_descriptor_data. + } + else + { + // We are shutting down, so prevent cleanup_descriptor_data from freeing + // the descriptor_data object and let the destructor free it instead. + descriptor_data = 0; } } @@ -388,6 +394,22 @@ void epoll_reactor::deregister_internal_descriptor(socket_type descriptor, descriptor_lock.unlock(); + // Leave descriptor_data set so that it will be freed by the subsequent + // call to cleanup_descriptor_data. + } + else + { + // We are shutting down, so prevent cleanup_descriptor_data from freeing + // the descriptor_data object and let the destructor free it instead. + descriptor_data = 0; + } +} + +void epoll_reactor::cleanup_descriptor_data( + per_descriptor_data& descriptor_data) +{ + if (descriptor_data) + { free_descriptor_state(descriptor_data); descriptor_data = 0; } @@ -451,8 +473,15 @@ void epoll_reactor::run(bool block, op_queue& ops) // don't call work_started() here. This still allows the io_service to // stop if the only remaining operations are descriptor operations. descriptor_state* descriptor_data = static_cast(ptr); - descriptor_data->set_ready_events(events[i].events); - ops.push(descriptor_data); + if (!ops.is_enqueued(descriptor_data)) + { + descriptor_data->set_ready_events(events[i].events); + ops.push(descriptor_data); + } + else + { + descriptor_data->add_ready_events(events[i].events); + } } } diff --git a/boost/asio/detail/impl/kqueue_reactor.ipp b/boost/asio/detail/impl/kqueue_reactor.ipp index b4a7a10b71..8057606ce6 100644 --- a/boost/asio/detail/impl/kqueue_reactor.ipp +++ b/boost/asio/detail/impl/kqueue_reactor.ipp @@ -312,10 +312,16 @@ void kqueue_reactor::deregister_descriptor(socket_type descriptor, descriptor_lock.unlock(); - free_descriptor_state(descriptor_data); - descriptor_data = 0; - io_service_.post_deferred_completions(ops); + + // Leave descriptor_data set so that it will be freed by the subsequent + // call to cleanup_descriptor_data. + } + else + { + // We are shutting down, so prevent cleanup_descriptor_data from freeing + // the descriptor_data object and let the destructor free it instead. + descriptor_data = 0; } } @@ -345,6 +351,22 @@ void kqueue_reactor::deregister_internal_descriptor(socket_type descriptor, descriptor_lock.unlock(); + // Leave descriptor_data set so that it will be freed by the subsequent + // call to cleanup_descriptor_data. + } + else + { + // We are shutting down, so prevent cleanup_descriptor_data from freeing + // the descriptor_data object and let the destructor free it instead. + descriptor_data = 0; + } +} + +void kqueue_reactor::cleanup_descriptor_data( + per_descriptor_data& descriptor_data) +{ + if (descriptor_data) + { free_descriptor_state(descriptor_data); descriptor_data = 0; } diff --git a/boost/asio/detail/impl/reactive_descriptor_service.ipp b/boost/asio/detail/impl/reactive_descriptor_service.ipp index 56caec9fd3..a0300c47d0 100644 --- a/boost/asio/detail/impl/reactive_descriptor_service.ipp +++ b/boost/asio/detail/impl/reactive_descriptor_service.ipp @@ -88,10 +88,12 @@ void reactive_descriptor_service::destroy( reactor_.deregister_descriptor(impl.descriptor_, impl.reactor_data_, (impl.state_ & descriptor_ops::possible_dup) == 0); - } - boost::system::error_code ignored_ec; - descriptor_ops::close(impl.descriptor_, impl.state_, ignored_ec); + boost::system::error_code ignored_ec; + descriptor_ops::close(impl.descriptor_, impl.state_, ignored_ec); + + reactor_.cleanup_descriptor_data(impl.reactor_data_); + } } boost::system::error_code reactive_descriptor_service::assign( @@ -128,9 +130,15 @@ boost::system::error_code reactive_descriptor_service::close( reactor_.deregister_descriptor(impl.descriptor_, impl.reactor_data_, (impl.state_ & descriptor_ops::possible_dup) == 0); - } - descriptor_ops::close(impl.descriptor_, impl.state_, ec); + descriptor_ops::close(impl.descriptor_, impl.state_, ec); + + reactor_.cleanup_descriptor_data(impl.reactor_data_); + } + else + { + ec = boost::system::error_code(); + } // The descriptor is closed by the OS even if close() returns an error. // @@ -154,6 +162,7 @@ reactive_descriptor_service::release( BOOST_ASIO_HANDLER_OPERATION(("descriptor", &impl, "release")); reactor_.deregister_descriptor(impl.descriptor_, impl.reactor_data_, false); + reactor_.cleanup_descriptor_data(impl.reactor_data_); construct(impl); } diff --git a/boost/asio/detail/impl/reactive_socket_service_base.ipp b/boost/asio/detail/impl/reactive_socket_service_base.ipp index 21e77e9f93..3594ae0528 100644 --- a/boost/asio/detail/impl/reactive_socket_service_base.ipp +++ b/boost/asio/detail/impl/reactive_socket_service_base.ipp @@ -89,6 +89,8 @@ void reactive_socket_service_base::destroy( boost::system::error_code ignored_ec; socket_ops::close(impl.socket_, impl.state_, true, ignored_ec); + + reactor_.cleanup_descriptor_data(impl.reactor_data_); } } @@ -102,9 +104,15 @@ boost::system::error_code reactive_socket_service_base::close( reactor_.deregister_descriptor(impl.socket_, impl.reactor_data_, (impl.state_ & socket_ops::possible_dup) == 0); - } - socket_ops::close(impl.socket_, impl.state_, false, ec); + socket_ops::close(impl.socket_, impl.state_, false, ec); + + reactor_.cleanup_descriptor_data(impl.reactor_data_); + } + else + { + ec = boost::system::error_code(); + } // The descriptor is closed by the OS even if close() returns an error. // @@ -224,7 +232,7 @@ void reactive_socket_service_base::start_accept_op( reactor_op* op, bool is_continuation, bool peer_is_open) { if (!peer_is_open) - start_op(impl, reactor::read_op, op, true, is_continuation, false); + start_op(impl, reactor::read_op, op, is_continuation, true, false); else { op->ec_ = boost::asio::error::already_open; diff --git a/boost/asio/detail/impl/select_reactor.ipp b/boost/asio/detail/impl/select_reactor.ipp index 80686eaf97..869f73492b 100644 --- a/boost/asio/detail/impl/select_reactor.ipp +++ b/boost/asio/detail/impl/select_reactor.ipp @@ -163,6 +163,11 @@ void select_reactor::deregister_internal_descriptor( op_queue_[i].cancel_operations(descriptor, ops); } +void select_reactor::cleanup_descriptor_data( + select_reactor::per_descriptor_data&) +{ +} + void select_reactor::run(bool block, op_queue& ops) { boost::asio::detail::mutex::scoped_lock lock(mutex_); diff --git a/boost/asio/detail/impl/signal_set_service.ipp b/boost/asio/detail/impl/signal_set_service.ipp index 56313e0923..95ea8bee21 100644 --- a/boost/asio/detail/impl/signal_set_service.ipp +++ b/boost/asio/detail/impl/signal_set_service.ipp @@ -187,6 +187,7 @@ void signal_set_service::fork_service( state->fork_prepared_ = true; lock.unlock(); reactor_.deregister_internal_descriptor(read_descriptor, reactor_data_); + reactor_.cleanup_descriptor_data(reactor_data_); } break; case boost::asio::io_service::fork_parent: @@ -539,8 +540,9 @@ void signal_set_service::remove_service(signal_set_service* service) // Disable the pipe readiness notifications. int read_descriptor = state->read_descriptor_; lock.unlock(); - service->reactor_.deregister_descriptor( - read_descriptor, service->reactor_data_, false); + service->reactor_.deregister_internal_descriptor( + read_descriptor, service->reactor_data_); + service->reactor_.cleanup_descriptor_data(service->reactor_data_); lock.lock(); #endif // !defined(BOOST_ASIO_WINDOWS) // && !defined(BOOST_ASIO_WINDOWS_RUNTIME) diff --git a/boost/asio/detail/impl/win_iocp_socket_service_base.ipp b/boost/asio/detail/impl/win_iocp_socket_service_base.ipp index 22818f7e92..93f5ed0713 100644 --- a/boost/asio/detail/impl/win_iocp_socket_service_base.ipp +++ b/boost/asio/detail/impl/win_iocp_socket_service_base.ipp @@ -177,9 +177,16 @@ boost::system::error_code win_iocp_socket_service_base::close( reinterpret_cast(&reactor_), 0, 0)); if (r) r->deregister_descriptor(impl.socket_, impl.reactor_data_, true); - } - socket_ops::close(impl.socket_, impl.state_, false, ec); + socket_ops::close(impl.socket_, impl.state_, false, ec); + + if (r) + r->cleanup_descriptor_data(impl.reactor_data_); + } + else + { + ec = boost::system::error_code(); + } impl.socket_ = invalid_socket; impl.state_ = 0; @@ -629,10 +636,14 @@ void win_iocp_socket_service_base::close_for_destruction( reinterpret_cast(&reactor_), 0, 0)); if (r) r->deregister_descriptor(impl.socket_, impl.reactor_data_, true); + + boost::system::error_code ignored_ec; + socket_ops::close(impl.socket_, impl.state_, true, ignored_ec); + + if (r) + r->cleanup_descriptor_data(impl.reactor_data_); } - boost::system::error_code ignored_ec; - socket_ops::close(impl.socket_, impl.state_, true, ignored_ec); impl.socket_ = invalid_socket; impl.state_ = 0; impl.cancel_token_.reset(); diff --git a/boost/asio/detail/kqueue_reactor.hpp b/boost/asio/detail/kqueue_reactor.hpp index 6aba2b264f..c6182b4d55 100644 --- a/boost/asio/detail/kqueue_reactor.hpp +++ b/boost/asio/detail/kqueue_reactor.hpp @@ -125,14 +125,22 @@ public: per_descriptor_data& descriptor_data); // Cancel any operations that are running against the descriptor and remove - // its registration from the reactor. + // its registration from the reactor. The reactor resources associated with + // the descriptor must be released by calling cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_descriptor(socket_type descriptor, per_descriptor_data& descriptor_data, bool closing); - // Remote the descriptor's registration from the reactor. + // Remove the descriptor's registration from the reactor. The reactor + // resources associated with the descriptor must be released by calling + // cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_internal_descriptor( socket_type descriptor, per_descriptor_data& descriptor_data); + // Perform any post-deregistration cleanup tasks associated with the + // descriptor data. + BOOST_ASIO_DECL void cleanup_descriptor_data( + per_descriptor_data& descriptor_data); + // Add a new timer queue to the reactor. template void add_timer_queue(timer_queue& queue); diff --git a/boost/asio/detail/op_queue.hpp b/boost/asio/detail/op_queue.hpp index 9605248567..89f318eedf 100644 --- a/boost/asio/detail/op_queue.hpp +++ b/boost/asio/detail/op_queue.hpp @@ -139,6 +139,12 @@ public: return front_ == 0; } + // Test whether an operation is already enqueued. + bool is_enqueued(Operation* o) const + { + return op_queue_access::next(o) != 0 || back_ == o; + } + private: friend class op_queue_access; diff --git a/boost/asio/detail/select_reactor.hpp b/boost/asio/detail/select_reactor.hpp index 69b04c82b5..d6f75242e5 100644 --- a/boost/asio/detail/select_reactor.hpp +++ b/boost/asio/detail/select_reactor.hpp @@ -107,13 +107,20 @@ public: BOOST_ASIO_DECL void cancel_ops(socket_type descriptor, per_descriptor_data&); // Cancel any operations that are running against the descriptor and remove - // its registration from the reactor. + // its registration from the reactor. The reactor resources associated with + // the descriptor must be released by calling cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_descriptor(socket_type descriptor, per_descriptor_data&, bool closing); - // Remote the descriptor's registration from the reactor. + // Remove the descriptor's registration from the reactor. The reactor + // resources associated with the descriptor must be released by calling + // cleanup_descriptor_data. BOOST_ASIO_DECL void deregister_internal_descriptor( - socket_type descriptor, per_descriptor_data& descriptor_data); + socket_type descriptor, per_descriptor_data&); + + // Perform any post-deregistration cleanup tasks associated with the + // descriptor data. + BOOST_ASIO_DECL void cleanup_descriptor_data(per_descriptor_data&); // Move descriptor registration from one descriptor_data object to another. BOOST_ASIO_DECL void move_descriptor(socket_type descriptor, diff --git a/boost/asio/detail/socket_types.hpp b/boost/asio/detail/socket_types.hpp index 8467118b54..0768f70077 100644 --- a/boost/asio/detail/socket_types.hpp +++ b/boost/asio/detail/socket_types.hpp @@ -57,7 +57,11 @@ # include #else # include -# if !defined(__SYMBIAN32__) +# if (defined(__MACH__) && defined(__APPLE__)) \ + || defined(__FreeBSD__) || defined(__NetBSD__) \ + || defined(__OpenBSD__) || defined(__linux__) +# include +# elif !defined(__SYMBIAN32__) # include # endif # include diff --git a/boost/asio/detail/winapi_thread.hpp b/boost/asio/detail/winapi_thread.hpp index 204e73f42a..79aba9b519 100644 --- a/boost/asio/detail/winapi_thread.hpp +++ b/boost/asio/detail/winapi_thread.hpp @@ -20,8 +20,8 @@ #if defined(BOOST_ASIO_WINDOWS) #if defined(BOOST_ASIO_WINDOWS_APP) || defined(UNDER_CE) -#include #include +#include #include #include #include @@ -42,7 +42,7 @@ public: template winapi_thread(Function f, unsigned int = 0) { - std::auto_ptr arg(new func(f)); + scoped_ptr arg(new func(f)); DWORD thread_id = 0; thread_ = ::CreateThread(0, 0, winapi_thread_function, arg.get(), 0, &thread_id); @@ -106,7 +106,7 @@ private: inline DWORD WINAPI winapi_thread_function(LPVOID arg) { - std::auto_ptr func( + scoped_ptr func( static_cast(arg)); func->run(); return 0; -- cgit v1.2.3