Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
7fa5dd74
Unverified
Commit
7fa5dd74
authored
Sep 26, 2019
by
Zachary Anderson
Committed by
GitHub
Sep 26, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter_tools] Allows adding multiple signal handlers (#41304)
parent
3fce2608
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
285 additions
and
10 deletions
+285
-10
signals.dart
packages/flutter_tools/lib/src/base/signals.dart
+130
-0
context_runner.dart
packages/flutter_tools/lib/src/context_runner.dart
+2
-0
resident_runner.dart
packages/flutter_tools/lib/src/resident_runner.dart
+5
-10
signals_test.dart
...s/flutter_tools/test/general.shard/base/signals_test.dart
+148
-0
No files found.
packages/flutter_tools/lib/src/base/signals.dart
0 → 100644
View file @
7fa5dd74
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'async_guard.dart'
;
import
'context.dart'
;
import
'io.dart'
;
typedef
SignalHandler
=
FutureOr
<
void
>
Function
(
ProcessSignal
signal
);
Signals
get
signals
=>
Signals
.
instance
;
/// A class that manages signal handlers
///
/// Signal handlers are run in the order that they were added.
abstract
class
Signals
{
factory
Signals
()
=>
_DefaultSignals
.
_
();
static
Signals
get
instance
=>
context
.
get
<
Signals
>();
/// Adds a signal handler to run on receipt of signal.
///
/// The handler will run after all handlers that were previously added for the
/// signal. The function returns an abstract token that should be provided to
/// removeHandler to remove the handler.
Object
addHandler
(
ProcessSignal
signal
,
SignalHandler
handler
);
/// Removes a signal handler.
///
/// Removes the signal handler for the signal identified by the abstract
/// token parameter. Returns true if the handler was removed and false
/// otherwise.
Future
<
bool
>
removeHandler
(
ProcessSignal
signal
,
Object
token
);
/// If a [SignalHandler] throws an error, either synchronously or
/// asynchronously, it will be added to this stream instead of propagated.
Stream
<
Object
>
get
errors
;
}
class
_DefaultSignals
implements
Signals
{
_DefaultSignals
.
_
();
// A table mapping (signal, token) -> signal handler.
final
Map
<
ProcessSignal
,
Map
<
Object
,
SignalHandler
>>
_handlersTable
=
<
ProcessSignal
,
Map
<
Object
,
SignalHandler
>>{};
// A table mapping (signal) -> signal handler list. The list is in the order
// that the signal handlers should be run.
final
Map
<
ProcessSignal
,
List
<
SignalHandler
>>
_handlersList
=
<
ProcessSignal
,
List
<
SignalHandler
>>{};
// A table mapping (signal) -> low-level signal event stream.
final
Map
<
ProcessSignal
,
StreamSubscription
<
ProcessSignal
>>
_streamSubscriptions
=
<
ProcessSignal
,
StreamSubscription
<
ProcessSignal
>>{};
// The stream controller for errors coming from signal handlers.
final
StreamController
<
Object
>
_errorStreamController
=
StreamController
<
Object
>.
broadcast
();
@override
Stream
<
Object
>
get
errors
=>
_errorStreamController
.
stream
;
@override
Object
addHandler
(
ProcessSignal
signal
,
SignalHandler
handler
)
{
final
Object
token
=
Object
();
_handlersTable
.
putIfAbsent
(
signal
,
()
=>
<
Object
,
SignalHandler
>{});
_handlersTable
[
signal
][
token
]
=
handler
;
_handlersList
.
putIfAbsent
(
signal
,
()
=>
<
SignalHandler
>[]);
_handlersList
[
signal
].
add
(
handler
);
// If we added the first one, then call signal.watch(), listen, and cache
// the stream controller.
if
(
_handlersList
[
signal
].
length
==
1
)
{
_streamSubscriptions
[
signal
]
=
signal
.
watch
().
listen
(
_handleSignal
);
}
return
token
;
}
@override
Future
<
bool
>
removeHandler
(
ProcessSignal
signal
,
Object
token
)
async
{
// We don't know about this signal.
if
(!
_handlersTable
.
containsKey
(
signal
))
{
return
false
;
}
// We don't know about this token.
if
(!
_handlersTable
[
signal
].
containsKey
(
token
))
{
return
false
;
}
final
SignalHandler
handler
=
_handlersTable
[
signal
][
token
];
final
bool
removed
=
_handlersList
[
signal
].
remove
(
handler
);
if
(!
removed
)
{
return
false
;
}
// If _handlersList[signal] is empty, then lookup the cached stream
// controller and unsubscribe from the stream.
if
(
_handlersList
.
isEmpty
)
{
await
_streamSubscriptions
[
signal
].
cancel
();
}
return
true
;
}
Future
<
void
>
_handleSignal
(
ProcessSignal
s
)
async
{
for
(
SignalHandler
handler
in
_handlersList
[
s
])
{
try
{
await
asyncGuard
<
void
>(()
=>
handler
(
s
));
}
catch
(
e
)
{
if
(
_errorStreamController
.
hasListener
)
{
_errorStreamController
.
add
(
e
);
}
}
}
// If this was a signal that should cause the process to go down, then
// call exit();
if
(
_shouldExitFor
(
s
))
{
exit
(
0
);
}
}
// The list of signals that should cause the process to exit.
static
const
List
<
ProcessSignal
>
_exitingSignals
=
<
ProcessSignal
>[
ProcessSignal
.
SIGTERM
,
ProcessSignal
.
SIGINT
,
ProcessSignal
.
SIGKILL
,
];
bool
_shouldExitFor
(
ProcessSignal
signal
)
=>
_exitingSignals
.
contains
(
signal
);
}
packages/flutter_tools/lib/src/context_runner.dart
View file @
7fa5dd74
...
@@ -20,6 +20,7 @@ import 'base/logger.dart';
...
@@ -20,6 +20,7 @@ import 'base/logger.dart';
import
'base/os.dart'
;
import
'base/os.dart'
;
import
'base/platform.dart'
;
import
'base/platform.dart'
;
import
'base/process.dart'
;
import
'base/process.dart'
;
import
'base/signals.dart'
;
import
'base/time.dart'
;
import
'base/time.dart'
;
import
'base/user_messages.dart'
;
import
'base/user_messages.dart'
;
import
'base/utils.dart'
;
import
'base/utils.dart'
;
...
@@ -106,6 +107,7 @@ Future<T> runInContext<T>(
...
@@ -106,6 +107,7 @@ Future<T> runInContext<T>(
OperatingSystemUtils:
()
=>
OperatingSystemUtils
(),
OperatingSystemUtils:
()
=>
OperatingSystemUtils
(),
ProcessInfo:
()
=>
ProcessInfo
(),
ProcessInfo:
()
=>
ProcessInfo
(),
ProcessUtils:
()
=>
ProcessUtils
(),
ProcessUtils:
()
=>
ProcessUtils
(),
Signals:
()
=>
Signals
(),
SimControl:
()
=>
SimControl
(),
SimControl:
()
=>
SimControl
(),
Stdio:
()
=>
const
Stdio
(),
Stdio:
()
=>
const
Stdio
(),
SystemClock:
()
=>
const
SystemClock
(),
SystemClock:
()
=>
const
SystemClock
(),
...
...
packages/flutter_tools/lib/src/resident_runner.dart
View file @
7fa5dd74
...
@@ -13,6 +13,7 @@ import 'base/common.dart';
...
@@ -13,6 +13,7 @@ import 'base/common.dart';
import
'base/file_system.dart'
;
import
'base/file_system.dart'
;
import
'base/io.dart'
as
io
;
import
'base/io.dart'
as
io
;
import
'base/logger.dart'
;
import
'base/logger.dart'
;
import
'base/signals.dart'
;
import
'base/terminal.dart'
;
import
'base/terminal.dart'
;
import
'base/utils.dart'
;
import
'base/utils.dart'
;
import
'build_info.dart'
;
import
'build_info.dart'
;
...
@@ -993,19 +994,13 @@ class TerminalHandler {
...
@@ -993,19 +994,13 @@ class TerminalHandler {
void
registerSignalHandlers
()
{
void
registerSignalHandlers
()
{
assert
(
residentRunner
.
stayResident
);
assert
(
residentRunner
.
stayResident
);
io
.
ProcessSignal
.
SIGINT
.
watch
().
listen
((
io
.
ProcessSignal
signal
)
{
signals
.
addHandler
(
io
.
ProcessSignal
.
SIGINT
,
_cleanUp
);
_cleanUp
(
signal
);
signals
.
addHandler
(
io
.
ProcessSignal
.
SIGTERM
,
_cleanUp
);
io
.
exit
(
0
);
});
io
.
ProcessSignal
.
SIGTERM
.
watch
().
listen
((
io
.
ProcessSignal
signal
)
{
_cleanUp
(
signal
);
io
.
exit
(
0
);
});
if
(!
residentRunner
.
supportsServiceProtocol
||
!
residentRunner
.
supportsRestart
)
{
if
(!
residentRunner
.
supportsServiceProtocol
||
!
residentRunner
.
supportsRestart
)
{
return
;
return
;
}
}
io
.
ProcessSignal
.
SIGUSR1
.
watch
().
listen
(
_handleSignal
);
signals
.
addHandler
(
io
.
ProcessSignal
.
SIGUSR1
,
_handleSignal
);
io
.
ProcessSignal
.
SIGUSR2
.
watch
().
listen
(
_handleSignal
);
signals
.
addHandler
(
io
.
ProcessSignal
.
SIGUSR2
,
_handleSignal
);
}
}
/// Returns [true] if the input has been handled by this function.
/// Returns [true] if the input has been handled by this function.
...
...
packages/flutter_tools/test/general.shard/base/signals_test.dart
0 → 100644
View file @
7fa5dd74
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:io'
as
io
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/signals.dart'
;
import
'package:mockito/mockito.dart'
;
import
'../../src/common.dart'
;
import
'../../src/context.dart'
;
void
main
(
)
{
group
(
'Signals'
,
()
{
MockIoProcessSignal
mockSignal
;
ProcessSignal
signalUnderTest
;
StreamController
<
io
.
ProcessSignal
>
controller
;
setUp
(()
{
mockSignal
=
MockIoProcessSignal
();
signalUnderTest
=
ProcessSignal
(
mockSignal
);
controller
=
StreamController
<
io
.
ProcessSignal
>();
when
(
mockSignal
.
watch
()).
thenAnswer
((
Invocation
invocation
)
=>
controller
.
stream
);
});
testUsingContext
(
'signal handler runs'
,
()
async
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
expect
(
s
,
signalUnderTest
);
completer
.
complete
();
});
controller
.
add
(
mockSignal
);
await
completer
.
future
;
});
testUsingContext
(
'signal handlers run in order'
,
()
async
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
bool
first
=
false
;
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
expect
(
s
,
signalUnderTest
);
first
=
true
;
});
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
expect
(
s
,
signalUnderTest
);
expect
(
first
,
isTrue
);
completer
.
complete
();
});
controller
.
add
(
mockSignal
);
await
completer
.
future
;
});
testUsingContext
(
'signal handler error goes on error stream'
,
()
async
{
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
throw
'Error'
;
});
final
Completer
<
void
>
completer
=
Completer
<
void
>();
final
List
<
Object
>
errList
=
<
Object
>[];
final
StreamSubscription
<
Object
>
errSub
=
signals
.
errors
.
listen
((
Object
err
)
{
errList
.
add
(
err
);
completer
.
complete
();
});
controller
.
add
(
mockSignal
);
await
completer
.
future
;
await
errSub
.
cancel
();
expect
(
errList
,
<
Object
>[
'Error'
]);
});
testUsingContext
(
'removed signal handler does not run'
,
()
async
{
final
Object
token
=
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
fail
(
'Signal handler should have been removed.'
);
});
await
signals
.
removeHandler
(
signalUnderTest
,
token
);
final
List
<
Object
>
errList
=
<
Object
>[];
final
StreamSubscription
<
Object
>
errSub
=
signals
.
errors
.
listen
((
Object
err
)
{
errList
.
add
(
err
);
});
controller
.
add
(
mockSignal
);
await
errSub
.
cancel
();
expect
(
errList
,
isEmpty
);
});
testUsingContext
(
'non-removed signal handler still runs'
,
()
async
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
expect
(
s
,
signalUnderTest
);
completer
.
complete
();
});
final
Object
token
=
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
fail
(
'Signal handler should have been removed.'
);
});
await
signals
.
removeHandler
(
signalUnderTest
,
token
);
final
List
<
Object
>
errList
=
<
Object
>[];
final
StreamSubscription
<
Object
>
errSub
=
signals
.
errors
.
listen
((
Object
err
)
{
errList
.
add
(
err
);
});
controller
.
add
(
mockSignal
);
await
completer
.
future
;
await
errSub
.
cancel
();
expect
(
errList
,
isEmpty
);
});
testUsingContext
(
'only handlers for the correct signal run'
,
()
async
{
final
MockIoProcessSignal
mockSignal2
=
MockIoProcessSignal
();
final
StreamController
<
io
.
ProcessSignal
>
controller2
=
StreamController
<
io
.
ProcessSignal
>();
final
ProcessSignal
otherSignal
=
ProcessSignal
(
mockSignal2
);
when
(
mockSignal2
.
watch
()).
thenAnswer
((
Invocation
invocation
)
=>
controller2
.
stream
);
final
Completer
<
void
>
completer
=
Completer
<
void
>();
signals
.
addHandler
(
signalUnderTest
,
(
ProcessSignal
s
)
{
expect
(
s
,
signalUnderTest
);
completer
.
complete
();
});
signals
.
addHandler
(
otherSignal
,
(
ProcessSignal
s
)
{
fail
(
'Wrong signal!.'
);
});
final
List
<
Object
>
errList
=
<
Object
>[];
final
StreamSubscription
<
Object
>
errSub
=
signals
.
errors
.
listen
((
Object
err
)
{
errList
.
add
(
err
);
});
controller
.
add
(
mockSignal
);
await
completer
.
future
;
await
errSub
.
cancel
();
expect
(
errList
,
isEmpty
);
});
});
}
class
MockIoProcessSignal
extends
Mock
implements
io
.
ProcessSignal
{}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment