1 /* Copyright 2017-2021 PaGMO development team
2 
3 This file is part of the PaGMO library.
4 
5 The PaGMO library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8   * the GNU Lesser General Public License as published by the Free
9     Software Foundation; either version 3 of the License, or (at your
10     option) any later version.
11 
12 or
13 
14   * the GNU General Public License as published by the Free Software
15     Foundation; either version 3 of the License, or (at your option) any
16     later version.
17 
18 or both in parallel, as here.
19 
20 The PaGMO library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the PaGMO library.  If not,
27 see https://www.gnu.org/licenses/. */
28 
29 #define BOOST_TEST_MODULE fork_island_test
30 #define BOOST_TEST_DYN_LINK
31 #include <boost/test/unit_test.hpp>
32 
33 #include <chrono>
34 #include <csignal>
35 #include <exception>
36 #include <stdexcept>
37 #include <thread>
38 #include <utility>
39 #include <vector>
40 
41 #include <sys/types.h>
42 
43 #include <boost/algorithm/string/predicate.hpp>
44 
45 #include <pagmo/algorithm.hpp>
46 #include <pagmo/algorithms/compass_search.hpp>
47 #include <pagmo/algorithms/de.hpp>
48 #include <pagmo/island.hpp>
49 #include <pagmo/islands/fork_island.hpp>
50 #include <pagmo/population.hpp>
51 #include <pagmo/problem.hpp>
52 #include <pagmo/problems/rosenbrock.hpp>
53 #include <pagmo/s11n.hpp>
54 #include <pagmo/types.hpp>
55 
56 using namespace pagmo;
57 
58 // A silly problem that, after max fitness evals, just waits.
59 struct godot1 {
godot1godot160     explicit godot1(unsigned max) : m_max(max), m_counter(0) {}
godot1godot161     godot1() : godot1(0) {}
fitnessgodot162     vector_double fitness(const vector_double &) const
63     {
64         if (m_max == m_counter++) {
65             std::this_thread::sleep_for(std::chrono::seconds(3600));
66         }
67         return {.5};
68     }
get_boundsgodot169     std::pair<vector_double, vector_double> get_bounds() const
70     {
71         return {{0.}, {1.}};
72     }
73     template <typename Archive>
serializegodot174     void serialize(Archive &ar, unsigned)
75     {
76         detail::archive(ar, m_max, m_counter);
77     }
78     unsigned m_max;
79     mutable unsigned m_counter;
80 };
81 
82 PAGMO_S11N_PROBLEM_EXPORT(godot1)
83 
BOOST_AUTO_TEST_CASE(fork_island_basic)84 BOOST_AUTO_TEST_CASE(fork_island_basic)
85 {
86     {
87         fork_island fi_0;
88         BOOST_CHECK(fi_0.get_child_pid() == pid_t(0));
89         fork_island fi_1(fi_0), fi_2(std::move(fi_0));
90         BOOST_CHECK(fi_1.get_child_pid() == pid_t(0));
91         BOOST_CHECK(fi_2.get_child_pid() == pid_t(0));
92         BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
93         BOOST_CHECK(boost::contains(fi_1.get_extra_info(), "No active child"));
94         BOOST_CHECK(boost::contains(fi_2.get_extra_info(), "No active child"));
95         BOOST_CHECK_EQUAL(fi_0.get_name(), "Fork island");
96     }
97     // NOTE: on recent OSX versions, the fork() behaviour changed and trying
98     // to do error handling via exceptions in the forked() process now does
99     // not seem to work any more. Let's disable the error handling tests
100     // for now, perhaps we can investigate this further in the future.
101 #if !defined(__APPLE__)
102     {
103         // Test: try to kill a running island.
104         island fi_0(fork_island{}, de{200}, godot1{20}, 20);
105         BOOST_CHECK(fi_0.extract<fork_island>() != nullptr);
106         BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
107         fi_0.evolve();
108         // Busy wait until the child is running.
109         pid_t child_pid;
110         while (!(child_pid = fi_0.extract<fork_island>()->get_child_pid())) {
111         }
112         BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "Child PID:"));
113         // """
114         // Kill the boy and let the man be born.
115         // """
116         kill(child_pid, SIGTERM);
117         // Check that killing the child raised an error in the parent process.
118         BOOST_CHECK_THROW(fi_0.wait_check(), std::exception);
119         BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
120     }
121     {
122         // Test: try to generate an error in the evolution.
123         // NOTE: de wants more than 1 individual in the pop.
124         island fi_0(fork_island{}, de{1}, rosenbrock{}, 1);
125         BOOST_CHECK(fi_0.extract<fork_island>() != nullptr);
126         BOOST_CHECK(boost::contains(fi_0.get_extra_info(), "No active child"));
127         fi_0.evolve();
128         BOOST_CHECK_EXCEPTION(fi_0.wait_check(), std::runtime_error, [](const std::runtime_error &re) {
129             return boost::contains(re.what(), "needs at least 5 individuals in the population");
130         });
131     }
132 #endif
133 }
134 
135 // Check that the population actually evolves.
BOOST_AUTO_TEST_CASE(fork_island_evolve)136 BOOST_AUTO_TEST_CASE(fork_island_evolve)
137 {
138     island fi_0(fork_island{}, compass_search{100}, rosenbrock{}, 1, 0);
139     const auto old_cf = fi_0.get_population().champion_f();
140     fi_0.evolve();
141     fi_0.wait_check();
142     const auto new_cf = fi_0.get_population().champion_f();
143     BOOST_CHECK(new_cf[0] < old_cf[0]);
144 }
145 
146 // An algorithm that changes its state at every evolve() call.
147 struct stateful_algo {
evolvestateful_algo148     population evolve(const population &pop) const
149     {
150         ++n_evolve;
151         return pop;
152     }
153     template <typename Archive>
serializestateful_algo154     void serialize(Archive &ar, unsigned)
155     {
156         ar &n_evolve;
157     }
158     mutable int n_evolve = 0;
159 };
160 
161 PAGMO_S11N_ALGORITHM_EXPORT(stateful_algo)
162 
163 // Check that the state of the algorithm is preserved.
BOOST_AUTO_TEST_CASE(fork_island_stateful_algo)164 BOOST_AUTO_TEST_CASE(fork_island_stateful_algo)
165 {
166     island fi_0(fork_island{}, stateful_algo{}, rosenbrock{}, 1, 0);
167     BOOST_CHECK(fi_0.get_algorithm().extract<stateful_algo>()->n_evolve == 0);
168     fi_0.evolve();
169     fi_0.wait_check();
170     BOOST_CHECK(fi_0.get_algorithm().extract<stateful_algo>()->n_evolve == 1);
171 }
172 
173 struct recursive_algo1 {
evolverecursive_algo1174     population evolve(const population &pop) const
175     {
176         island fi_0(fork_island{}, compass_search{100}, pop);
177         fi_0.evolve();
178         fi_0.wait_check();
179         return fi_0.get_population();
180     }
181     template <typename Archive>
serializerecursive_algo1182     void serialize(Archive &, unsigned)
183     {
184     }
185 };
186 
187 struct recursive_algo2 {
evolverecursive_algo2188     population evolve(const population &pop) const
189     {
190         island fi_0(fork_island{}, de{1}, pop);
191         fi_0.evolve();
192         fi_0.wait_check();
193         return fi_0.get_population();
194     }
195     template <typename Archive>
serializerecursive_algo2196     void serialize(Archive &, unsigned)
197     {
198     }
199 };
200 
201 PAGMO_S11N_ALGORITHM_EXPORT(recursive_algo1)
PAGMO_S11N_ALGORITHM_EXPORT(recursive_algo2)202 PAGMO_S11N_ALGORITHM_EXPORT(recursive_algo2)
203 
204 // Try to call fork() inside fork().
205 BOOST_AUTO_TEST_CASE(fork_island_recurse)
206 {
207     {
208         island fi_0(fork_island{}, recursive_algo1{}, rosenbrock{}, 1, 0);
209         const auto old_cf = fi_0.get_population().champion_f();
210         fi_0.evolve();
211         fi_0.wait_check();
212         const auto new_cf = fi_0.get_population().champion_f();
213         BOOST_CHECK(new_cf[0] < old_cf[0]);
214     }
215 #if !defined(__APPLE__)
216     {
217         // Try also error transport.
218         island fi_0(fork_island{}, recursive_algo2{}, rosenbrock{}, 1, 0);
219         fi_0.evolve();
220         BOOST_CHECK_EXCEPTION(fi_0.wait_check(), std::runtime_error, [](const std::runtime_error &re) {
221             return boost::contains(re.what(), "needs at least 5 individuals in the population");
222         });
223     }
224 #endif
225 }
226 
227 // Run a moderate amount of fork islands in parallel.
BOOST_AUTO_TEST_CASE(fork_island_torture)228 BOOST_AUTO_TEST_CASE(fork_island_torture)
229 {
230     std::vector<island> visl(100u, island(fork_island{}, compass_search{100}, rosenbrock{100}, 50, 0));
231     for (auto &isl : visl) {
232         isl.evolve();
233     }
234     for (auto &isl : visl) {
235         BOOST_CHECK_NO_THROW(isl.wait_check());
236     }
237 }
238