Skip to content

Commit e7f7989

Browse files
authored
Merge pull request #1020 from rust-lang/excessive-execution
Nudge the user to kill programs using excessive CPU
2 parents d544d19 + 265ae26 commit e7f7989

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+585
-287
lines changed

compiler/base/orchestrator/src/coordinator.rs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use futures::{
22
future::{BoxFuture, OptionFuture},
3-
Future, FutureExt,
3+
stream::BoxStream,
4+
Future, FutureExt, StreamExt,
45
};
56
use once_cell::sync::Lazy;
67
use serde::Deserialize;
@@ -23,7 +24,7 @@ use tokio::{
2324
task::{JoinHandle, JoinSet},
2425
time::{self, MissedTickBehavior},
2526
};
26-
use tokio_stream::{wrappers::ReceiverStream, StreamExt};
27+
use tokio_stream::wrappers::ReceiverStream;
2728
use tokio_util::{io::SyncIoBridge, sync::CancellationToken};
2829
use tracing::{instrument, trace, trace_span, warn, Instrument};
2930

@@ -416,6 +417,25 @@ impl CargoTomlModifier for ExecuteRequest {
416417
}
417418
}
418419

420+
#[derive(Debug, Clone)]
421+
pub struct ExecuteStatus {
422+
pub resident_set_size_bytes: u64,
423+
pub total_time_secs: f64,
424+
}
425+
426+
impl From<CommandStatistics> for ExecuteStatus {
427+
fn from(value: CommandStatistics) -> Self {
428+
let CommandStatistics {
429+
total_time_secs,
430+
resident_set_size_bytes,
431+
} = value;
432+
Self {
433+
resident_set_size_bytes,
434+
total_time_secs,
435+
}
436+
}
437+
}
438+
419439
#[derive(Debug, Clone)]
420440
pub struct ExecuteResponse {
421441
pub success: bool,
@@ -1257,6 +1277,19 @@ impl Container {
12571277
}
12581278
.boxed();
12591279

1280+
let status_rx = tokio_stream::wrappers::ReceiverStream::new(status_rx)
1281+
.map(|s| {
1282+
let CommandStatistics {
1283+
total_time_secs,
1284+
resident_set_size_bytes,
1285+
} = s;
1286+
ExecuteStatus {
1287+
resident_set_size_bytes,
1288+
total_time_secs,
1289+
}
1290+
})
1291+
.boxed();
1292+
12601293
Ok(ActiveExecution {
12611294
task,
12621295
stdin_tx,
@@ -1781,7 +1814,7 @@ pub struct ActiveExecution {
17811814
pub stdin_tx: mpsc::Sender<String>,
17821815
pub stdout_rx: mpsc::Receiver<String>,
17831816
pub stderr_rx: mpsc::Receiver<String>,
1784-
pub status_rx: mpsc::Receiver<CommandStatistics>,
1817+
pub status_rx: BoxStream<'static, ExecuteStatus>,
17851818
}
17861819

17871820
impl fmt::Debug for ActiveExecution {
@@ -3197,19 +3230,13 @@ mod tests {
31973230
stdin_tx: _,
31983231
stdout_rx,
31993232
stderr_rx,
3200-
mut status_rx,
3233+
status_rx,
32013234
} = coordinator
32023235
.begin_execute(token.clone(), request)
32033236
.await
32043237
.unwrap();
32053238

3206-
let statuses = async {
3207-
let mut statuses = Vec::new();
3208-
while let Some(s) = status_rx.recv().await {
3209-
statuses.push(s);
3210-
}
3211-
statuses
3212-
};
3239+
let statuses = status_rx.collect::<Vec<_>>();
32133240

32143241
let output = WithOutput::try_absorb(task, stdout_rx, stderr_rx);
32153242

tests/Gemfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
GEM
22
remote: https://rubygems.org/
33
specs:
4-
addressable (2.8.5)
4+
addressable (2.8.6)
55
public_suffix (>= 2.0.2, < 6.0)
66
capybara (3.39.2)
77
addressable
@@ -29,7 +29,7 @@ GEM
2929
rack (3.0.8)
3030
rack-test (2.1.0)
3131
rack (>= 1.3)
32-
regexp_parser (2.8.2)
32+
regexp_parser (2.8.3)
3333
rexml (3.2.6)
3434
rspec (3.12.0)
3535
rspec-core (~> 3.12.0)
@@ -45,7 +45,7 @@ GEM
4545
rspec-support (~> 3.12.0)
4646
rspec-support (3.12.1)
4747
rubyzip (2.3.2)
48-
selenium-webdriver (4.15.0)
48+
selenium-webdriver (4.16.0)
4949
rexml (~> 3.2, >= 3.2.5)
5050
rubyzip (>= 1.2.2, < 3.0)
5151
websocket (~> 1.0)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
require 'json'
2+
3+
require 'spec_helper'
4+
require 'support/editor'
5+
require 'support/playground_actions'
6+
7+
RSpec.feature "Excessive executions", type: :feature, js: true do
8+
include PlaygroundActions
9+
10+
before do
11+
visit "/?#{config_overrides}"
12+
editor.set(code)
13+
end
14+
15+
scenario "a notification is shown" do
16+
within(:header) { click_on("Run") }
17+
within(:notification, text: 'will be automatically killed') do
18+
expect(page).to have_button 'Kill the process now'
19+
expect(page).to have_button 'Allow the process to continue'
20+
end
21+
end
22+
23+
scenario "the process is automatically killed if nothing is done" do
24+
within(:header) { click_on("Run") }
25+
expect(page).to have_selector(:notification, text: 'will be automatically killed', wait: 2)
26+
expect(page).to_not have_selector(:notification, text: 'will be automatically killed', wait: 4)
27+
expect(page).to have_content("Exited with signal 9")
28+
end
29+
30+
scenario "the process can continue running" do
31+
within(:header) { click_on("Run") }
32+
within(:notification, text: 'will be automatically killed') do
33+
click_on 'Allow the process to continue'
34+
end
35+
within(:output, :stdout) do
36+
expect(page).to have_content("Exited normally")
37+
end
38+
end
39+
40+
def editor
41+
Editor.new(page)
42+
end
43+
44+
def code
45+
<<~EOF
46+
use std::time::{Duration, Instant};
47+
48+
fn main() {
49+
let start = Instant::now();
50+
while start.elapsed() < Duration::from_secs(5) {}
51+
println!("Exited normally");
52+
}
53+
EOF
54+
end
55+
56+
def config_overrides
57+
config = {
58+
killGracePeriodS: 3.0,
59+
excessiveExecutionTimeS: 0.5,
60+
}
61+
62+
"whte_rbt.obj=#{config.to_json}"
63+
end
64+
end

tests/spec/spec_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
css { '[data-test-id = "stdin"]' }
8484
end
8585

86+
Capybara.add_selector(:notification) do
87+
css { '[data-test-id = "notification"]' }
88+
end
89+
8690
RSpec.configure do |config|
8791
config.after(:example, :js) do
8892
page.execute_script <<~JS

ui/frontend/.eslintrc.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ module.exports = {
1515
code: 120,
1616
},
1717
],
18+
'no-restricted-syntax': [
19+
'error',
20+
{
21+
message: 'Use `useAppDispatch` instead',
22+
selector: 'CallExpression[callee.name="useDispatch"]',
23+
},
24+
{
25+
message: 'Use `useAppSelector` instead',
26+
selector: 'CallExpression[callee.name="useSelector"]',
27+
},
28+
],
1829
'no-trailing-spaces': 'error',
1930
quotes: ['error', 'single'],
2031

@@ -65,13 +76,17 @@ module.exports = {
6576
'BuildMenu.tsx',
6677
'ButtonSet.tsx',
6778
'Header.tsx',
79+
'Notifications.tsx',
6880
'PopButton.tsx',
6981
'Stdin.tsx',
7082
'actions.ts',
7183
'api.ts',
7284
'compileActions.ts',
85+
'configureStore.ts',
7386
'editor/AceEditor.tsx',
7487
'editor/SimpleEditor.tsx',
88+
'hooks.ts',
89+
'observer.ts',
7590
'reducers/browser.ts',
7691
'reducers/client.ts',
7792
'reducers/code.ts',

ui/frontend/.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ node_modules
1616
!BuildMenu.tsx
1717
!ButtonSet.tsx
1818
!Header.tsx
19+
!Notifications.tsx
1920
!PopButton.tsx
2021
!Stdin.tsx
2122
!actions.ts
2223
!api.ts
2324
!compileActions.ts
25+
!configureStore.ts
2426
!editor/AceEditor.tsx
2527
!editor/SimpleEditor.tsx
28+
!hooks.ts
29+
!observer.ts
2630
!reducers/browser.ts
2731
!reducers/client.ts
2832
!reducers/code.ts

ui/frontend/AdvancedOptionsMenu.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import React, { useCallback } from 'react';
2-
import { useSelector, useDispatch } from 'react-redux';
32

43
import * as config from './reducers/configuration';
54
import { Either as EitherConfig, Select as SelectConfig } from './ConfigElement';
65
import MenuGroup from './MenuGroup';
7-
import { State } from './reducers';
86
import * as selectors from './selectors';
97
import { Backtrace, Channel, Edition } from './types';
8+
import { useAppDispatch, useAppSelector } from './hooks';
109

1110
const AdvancedOptionsMenu: React.FC = () => {
12-
const isEditionDefault = useSelector(selectors.isEditionDefault);
13-
const edition = useSelector((state: State) => state.configuration.edition);
14-
const isBacktraceSet = useSelector(selectors.getBacktraceSet);
15-
const backtrace = useSelector((state: State) => state.configuration.backtrace);
11+
const isEditionDefault = useAppSelector(selectors.isEditionDefault);
12+
const edition = useAppSelector((state) => state.configuration.edition);
13+
const isBacktraceSet = useAppSelector(selectors.getBacktraceSet);
14+
const backtrace = useAppSelector((state) => state.configuration.backtrace);
1615

17-
const dispatch = useDispatch();
16+
const dispatch = useAppDispatch();
1817

1918
const changeEdition = useCallback((e: Edition) => dispatch(config.changeEdition(e)), [dispatch]);
2019
const changeBacktrace = useCallback((b: Backtrace) => dispatch(config.changeBacktrace(b)), [dispatch]);
2120

22-
const channel = useSelector((state: State) => state.configuration.channel);
21+
const channel = useAppSelector((state) => state.configuration.channel);
2322
const switchText = (channel !== Channel.Nightly) ? ' (will select nightly Rust)' : '';
2423

2524
return (

ui/frontend/BuildMenu.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import React, { useCallback } from 'react';
2-
import { useSelector } from 'react-redux';
32

43
import ButtonMenuItem from './ButtonMenuItem';
54
import MenuAside from './MenuAside';
65
import MenuGroup from './MenuGroup';
76
import * as actions from './actions';
8-
import { useAppDispatch } from './configureStore';
7+
import { useAppDispatch, useAppSelector } from './hooks';
98
import * as selectors from './selectors';
109

1110
import styles from './BuildMenu.module.css';
@@ -14,7 +13,7 @@ interface BuildMenuProps {
1413
close: () => void;
1514
}
1615

17-
const useDispatchAndClose = (action: () => actions.ThunkAction, close: () => void) => {
16+
const useAppDispatchAndClose = (action: () => actions.ThunkAction, close: () => void) => {
1817
const dispatch = useAppDispatch();
1918

2019
return useCallback(() => {
@@ -24,17 +23,17 @@ const useDispatchAndClose = (action: () => actions.ThunkAction, close: () => voi
2423
};
2524

2625
const BuildMenu: React.FC<BuildMenuProps> = (props) => {
27-
const isHirAvailable = useSelector(selectors.isHirAvailable);
28-
const wasmLikelyToWork = useSelector(selectors.wasmLikelyToWork);
26+
const isHirAvailable = useAppSelector(selectors.isHirAvailable);
27+
const wasmLikelyToWork = useAppSelector(selectors.wasmLikelyToWork);
2928

30-
const compile = useDispatchAndClose(actions.performCompile, props.close);
31-
const compileToAssembly = useDispatchAndClose(actions.performCompileToAssembly, props.close);
32-
const compileToLLVM = useDispatchAndClose(actions.performCompileToLLVM, props.close);
33-
const compileToMir = useDispatchAndClose(actions.performCompileToMir, props.close);
34-
const compileToHir = useDispatchAndClose(actions.performCompileToNightlyHir, props.close);
35-
const compileToWasm = useDispatchAndClose(actions.performCompileToWasm, props.close);
36-
const execute = useDispatchAndClose(actions.performExecute, props.close);
37-
const test = useDispatchAndClose(actions.performTest, props.close);
29+
const compile = useAppDispatchAndClose(actions.performCompile, props.close);
30+
const compileToAssembly = useAppDispatchAndClose(actions.performCompileToAssembly, props.close);
31+
const compileToLLVM = useAppDispatchAndClose(actions.performCompileToLLVM, props.close);
32+
const compileToMir = useAppDispatchAndClose(actions.performCompileToMir, props.close);
33+
const compileToHir = useAppDispatchAndClose(actions.performCompileToNightlyHir, props.close);
34+
const compileToWasm = useAppDispatchAndClose(actions.performCompileToWasm, props.close);
35+
const execute = useAppDispatchAndClose(actions.performExecute, props.close);
36+
const test = useAppDispatchAndClose(actions.performTest, props.close);
3837

3938
return (
4039
<MenuGroup title="What do you want to do?">

ui/frontend/ChannelMenu.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import React, { Fragment, useCallback } from 'react';
2-
import { useSelector, useDispatch } from 'react-redux';
32

43
import MenuGroup from './MenuGroup';
54
import SelectOne from './SelectOne';
65

76
import * as config from './reducers/configuration';
87
import * as selectors from './selectors';
9-
import State from './state';
108
import { Channel } from './types';
9+
import { useAppDispatch, useAppSelector } from './hooks';
1110

1211
import styles from './ChannelMenu.module.css';
1312

@@ -16,14 +15,14 @@ interface ChannelMenuProps {
1615
}
1716

1817
const ChannelMenu: React.FC<ChannelMenuProps> = props => {
19-
const channel = useSelector((state: State) => state.configuration.channel);
20-
const stableVersion = useSelector(selectors.stableVersionText);
21-
const betaVersion = useSelector(selectors.betaVersionText);
22-
const nightlyVersion = useSelector(selectors.nightlyVersionText);
23-
const betaVersionDetails = useSelector(selectors.betaVersionDetailsText);
24-
const nightlyVersionDetails = useSelector(selectors.nightlyVersionDetailsText);
18+
const channel = useAppSelector((state) => state.configuration.channel);
19+
const stableVersion = useAppSelector(selectors.stableVersionText);
20+
const betaVersion = useAppSelector(selectors.betaVersionText);
21+
const nightlyVersion = useAppSelector(selectors.nightlyVersionText);
22+
const betaVersionDetails = useAppSelector(selectors.betaVersionDetailsText);
23+
const nightlyVersionDetails = useAppSelector(selectors.nightlyVersionDetailsText);
2524

26-
const dispatch = useDispatch();
25+
const dispatch = useAppDispatch();
2726
const changeChannel = useCallback((channel: Channel) => {
2827
dispatch(config.changeChannel(channel));
2928
props.close();

0 commit comments

Comments
 (0)