1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! Tests related to panics: which ones stop the node, which ones do not, which tasks are essential,
18
//! etc.
19

            
20
use {super::*, crate::function_name};
21

            
22
// This test is from sc_cli:
23
// https://github.com/paritytech/polkadot-sdk/blob/39b1f50f1c251def87c1625d68567ed252dc6272/substrate/client/cli/src/runner.rs#L363
24
/// This test ensures that `run_node_until_exit` aborts waiting for "stuck" tasks after 60
25
/// seconds, aka doesn't wait until they are finished (which may never happen).
26
#[test]
27
#[ignore = "takes 60 seconds to run"]
28
fn ensure_run_until_exit_is_not_blocking_indefinitely() {
29
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
30
        let runner = create_runner();
31

            
32
        runner
33
            .run_node_until_exit(move |cfg| async move {
34
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
35
                let (sender, receiver) = futures::channel::oneshot::channel();
36

            
37
                // We need to use `spawn_blocking` here so that we get a dedicated thread
38
                // for our future. This future is more blocking code that will never end.
39
                task_manager
40
                    .spawn_handle()
41
                    .spawn_blocking("test", None, async move {
42
                        let _ = sender.send(());
43
                        loop {
44
                            std::thread::sleep(Duration::from_secs(30));
45
                        }
46
                    });
47

            
48
                task_manager
49
                    .spawn_essential_handle()
50
                    .spawn_blocking("test2", None, async {
51
                        // Let's stop this essential task directly when our other task
52
                        // started. It will signal that the task manager should end.
53
                        let _ = receiver.await;
54
                    });
55

            
56
                Ok::<_, sc_service::Error>(task_manager)
57
            })
58
            .unwrap();
59
    });
60

            
61
    let Some(output) = output else { return };
62

            
63
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
64

            
65
    assert!(stderr.contains("Task \"test\" was still running after waiting 60 seconds to finish."));
66
    assert!(
67
        !stderr.contains("Task \"test2\" was still running after waiting 60 seconds to finish.")
68
    );
69
}
70

            
71
#[test]
72
2
fn node_stops_if_blocking_task_panics() {
73
2
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
74
1
        let runner = create_runner();
75
1

            
76
1
        runner
77
1
            .run_node_until_exit(move |cfg| async move {
78
1
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
79
1

            
80
1
                task_manager
81
1
                    .spawn_handle()
82
1
                    .spawn_blocking("test", None, async move {
83
1
                        panic!("spawn_blocking panicked");
84
1
                    });
85
1

            
86
1
                Ok::<_, sc_service::Error>(task_manager)
87
1
            })
88
1
            .unwrap();
89
2
    });
90

            
91
2
    let Some(output) = output else { return };
92

            
93
2
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
94
2

            
95
2
    assert!(stderr.contains("Thread 'tokio-runtime-worker' panicked at 'spawn_blocking panicked',"));
96
1
}
97

            
98
#[test]
99
2
fn node_stops_if_non_essential_task_panics() {
100
2
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
101
1
        let runner = create_runner();
102
1

            
103
1
        runner
104
1
            .run_node_until_exit(move |cfg| async move {
105
1
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
106
1

            
107
1
                task_manager.spawn_handle().spawn("test", None, async move {
108
1
                    panic!("non-essential task panicked");
109
1
                });
110
1

            
111
1
                Ok::<_, sc_service::Error>(task_manager)
112
1
            })
113
1
            .unwrap();
114
2
    });
115

            
116
2
    let Some(output) = output else { return };
117

            
118
2
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
119
2

            
120
2
    assert!(
121
2
        stderr.contains("Thread 'tokio-runtime-worker' panicked at 'non-essential task panicked',")
122
2
    );
123
1
}
124

            
125
#[test]
126
2
fn node_stops_if_essential_task_panics() {
127
2
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
128
1
        let runner = create_runner();
129
1

            
130
1
        runner
131
1
            .run_node_until_exit(move |cfg| async move {
132
1
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
133
1

            
134
1
                task_manager
135
1
                    .spawn_essential_handle()
136
1
                    .spawn("test", None, async move {
137
1
                        panic!("essential task panicked");
138
1
                    });
139
1

            
140
1
                Ok::<_, sc_service::Error>(task_manager)
141
1
            })
142
1
            .unwrap();
143
2
    });
144

            
145
2
    let Some(output) = output else { return };
146

            
147
2
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
148
2

            
149
2
    assert!(stderr.contains("Thread 'tokio-runtime-worker' panicked at 'essential task panicked',"));
150
1
}
151

            
152
#[test]
153
2
fn node_stops_if_essential_task_finishes() {
154
2
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
155
1
        let runner = create_runner();
156
1

            
157
1
        runner
158
1
            .run_node_until_exit(move |cfg| async move {
159
1
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
160
1

            
161
1
                task_manager
162
1
                    .spawn_essential_handle()
163
1
                    .spawn("test", None, async move {
164
1
                        // Sleep for 2 seconds and return.
165
1
                        // An essential task that returns should cause the task manager to stop.
166
1
                        tokio::time::sleep(Duration::from_secs(2)).await;
167
1
                    });
168
1

            
169
1
                Ok::<_, sc_service::Error>(task_manager)
170
1
            })
171
1
            .unwrap();
172
2
    });
173

            
174
2
    let Some(output) = output else { return };
175

            
176
2
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
177
2

            
178
2
    assert!(stderr.contains("Essential task `test` failed. Shutting down service."));
179
1
}
180

            
181
#[test]
182
2
fn node_stops_if_rust_thread_panics() {
183
2
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
184
1
        let runner = create_runner();
185
1

            
186
1
        runner
187
1
            .run_node_until_exit(move |cfg| async move {
188
1
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
189
1

            
190
1
                std::thread::spawn(|| panic!("rust thread panicked"));
191
1

            
192
1
                Ok::<_, sc_service::Error>(task_manager)
193
1
            })
194
1
            .unwrap_err();
195
2
    });
196

            
197
2
    let Some(output) = output else { return };
198

            
199
2
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
200
2
    assert!(stderr.contains("Thread '<unnamed>' panicked at 'rust thread panicked',"));
201
1
}
202

            
203
#[test]
204
#[ignore = "takes 10 seconds to run"]
205
fn node_does_not_stop_if_non_essential_task_finishes() {
206
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
207
        let runner = create_runner();
208

            
209
        runner
210
            .run_node_until_exit(move |cfg| async move {
211
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
212

            
213
                task_manager
214
                    .spawn_handle()
215
                    .spawn("test1", None, async move {
216
                        // Sleep for 2 seconds and return.
217
                        // A non-essential task that returns should not cause the task manager to stop.
218
                        tokio::time::sleep(Duration::from_secs(2)).await;
219
                    });
220

            
221
                task_manager
222
                    .spawn_essential_handle()
223
                    .spawn("test2", None, async move {
224
                        // Sleep for 10 seconds and return.
225
                        // An essential task that returns should cause the task manager to stop.
226
                        // Therefore this node should stop after 10 seconds.
227
                        tokio::time::sleep(Duration::from_secs(10)).await;
228
                    });
229

            
230
                Ok::<_, sc_service::Error>(task_manager)
231
            })
232
            .unwrap();
233
    });
234

            
235
    let Some(output) = output else { return };
236

            
237
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
238

            
239
    assert!(stderr.contains("Essential task `test2` failed. Shutting down service."));
240
    assert!(!stderr.contains("test1"));
241
}
242

            
243
#[test]
244
2
fn catch_unwind_example() {
245
2
    let output = run_test_in_another_process_expect_error(function_name!(), 1, || {
246
1
        let runner = create_runner();
247
1

            
248
1
        runner
249
1
            .run_node_until_exit(move |cfg| async move {
250
1
                let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
251
1

            
252
1
                // Because of the custom panic hook set by create_runner, using catch_unwind is
253
1
                // only possible in a single-threaded context after calling force_unwind.
254
1
                {
255
1
                    let _guard = sp_panic_handler::AbortGuard::force_unwind();
256
1

            
257
1
                    std::panic::catch_unwind(|| {
258
1
                        panic!("First panic did not stop the node");
259
1
                    })
260
1
                    .unwrap_err();
261
1
                }
262
1

            
263
1
                // We dropped the guard, the default behavior is to abort
264
1
                std::panic::catch_unwind(|| {
265
1
                    panic!("Second panic should not stop the node, but it does");
266
1
                })
267
1
                .unwrap_err();
268
1

            
269
1
                Ok::<_, sc_service::Error>(task_manager)
270
1
            })
271
1
            .unwrap();
272
1

            
273
1
        panic!("Third panic, unreachable");
274
2
    });
275

            
276
2
    let Some(output) = output else { return };
277

            
278
2
    let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
279
2
    assert!(stderr.contains(" panicked at 'First panic did not stop the node',"));
280
1
    assert!(stderr.contains(" panicked at 'Second panic should not stop the node, but it does',"));
281
1
    assert!(!stderr.contains("Third panic, unreachable"));
282
1
    assert_eq!(stderr.matches(" panicked at ").count(), 2)
283
1
}