diff --git a/dvc/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -7,6 +7,7 @@
 import os
 import subprocess
 import logging
+import signal
 
 from dvc.utils import relpath
 from dvc.utils.compat import pathlib
@@ -767,16 +768,22 @@ def _run(self):
         executable = os.getenv("SHELL") if os.name != "nt" else None
         self._warn_if_fish(executable)
 
-        p = subprocess.Popen(
-            self.cmd,
-            cwd=self.wdir,
-            shell=True,
-            env=fix_env(os.environ),
-            executable=executable,
-        )
-        p.communicate()
+        old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+        p = None
+
+        try:
+            p = subprocess.Popen(
+                self.cmd,
+                cwd=self.wdir,
+                shell=True,
+                env=fix_env(os.environ),
+                executable=executable,
+            )
+            p.communicate()
+        finally:
+            signal.signal(signal.SIGINT, old_handler)
 
-        if p.returncode != 0:
+        if (p is None) or (p.returncode != 0):
             raise StageCmdFailedError(self)
 
     def run(self, dry=False, resume=False, no_commit=False, force=False):

diff --git a/tests/func/test_run.py b/tests/func/test_run.py
--- a/tests/func/test_run.py
+++ b/tests/func/test_run.py
@@ -6,6 +6,7 @@
 import shutil
 import filecmp
 import subprocess
+import signal
 
 from dvc.main import main
 from dvc.output import OutputBase
@@ -323,9 +324,46 @@ def test_run_args_with_spaces(self):
         self.assertEqual(ret, 0)
         self.assertEqual(stage.cmd, 'echo "foo bar"')
 
-    @mock.patch.object(subprocess, "Popen", side_effect=KeyboardInterrupt)
-    def test_keyboard_interrupt(self, _):
-        ret = main(["run", "mycmd"])
+    @mock.patch.object(subprocess.Popen, "wait", new=KeyboardInterrupt)
+    def test_keyboard_interrupt(self):
+        ret = main(
+            [
+                "run",
+                "-d",
+                self.FOO,
+                "-d",
+                self.CODE,
+                "-o",
+                "out",
+                "-f",
+                "out.dvc",
+                "python",
+                self.CODE,
+                self.FOO,
+                "out",
+            ]
+        )
+        self.assertEqual(ret, 1)
+
+    @mock.patch.object(signal, "signal", side_effect=[None, KeyboardInterrupt])
+    def test_keyboard_interrupt_after_second_signal_call(self, _):
+        ret = main(
+            [
+                "run",
+                "-d",
+                self.FOO,
+                "-d",
+                self.CODE,
+                "-o",
+                "out",
+                "-f",
+                "out.dvc",
+                "python",
+                self.CODE,
+                self.FOO,
+                "out",
+            ]
+        )
         self.assertEqual(ret, 252)
 
 

run: handle SIGINT gracefully
https://discordapp.com/channels/485586884165107732/485596304961962003/543461241289572352

tests/func/test_run.py::TestCmdRun::test_keyboard_interrupt_after_second_signal_call
tests/func/test_run.py::TestRunBadWdir::test tests/func/test_run.py::TestNewRunShouldRemoveOutsOnNoPersist::test tests/func/test_run.py::TestRunNoExec::test tests/func/test_run.py::TestRunBadWdir::test_same_prefix tests/func/test_run.py::TestRunDeterministicChangedOut::test tests/func/test_run.py::TestRunBadName::test tests/func/test_run.py::TestRunCommit::test tests/func/test_run.py::TestNewRunShouldNotRemoveOutsOnPersist::test tests/func/test_run.py::TestRunBadName::test_same_prefix tests/func/test_run.py::TestRunBadCwd::test tests/func/test_run.py::TestRunBadName::test_not_found tests/func/test_run.py::TestRunDuplicatedArguments::test tests/func/test_run.py::TestRunDuplicatedArguments::test_non_normalized_paths tests/func/test_run.py::TestRunStageInsideOutput::test_file_name tests/func/test_run.py::TestRunDuplicatedArguments::test_outs_no_cache tests/func/test_run.py::TestRun::test tests/func/test_run.py::TestCmdRunCliMetrics::test_not_cached tests/func/test_run.py::TestRunPersistOutsNoCache::test tests/func/test_run.py::TestCmdRun::test_run tests/func/test_run.py::TestRunMissingDep::test tests/func/test_run.py::TestRunEmpty::test tests/func/test_run.py::TestShouldRaiseOnOverlappingOutputPaths::test tests/func/test_run.py::TestRunRemoveOuts::test tests/func/test_run.py::TestRunDeterministicChangedDep::test tests/func/test_run.py::TestShouldNotCheckoutUponCorruptedLocalHardlinkCache::test tests/func/test_run.py::TestCmdRun::test_run_args_from_cli tests/func/test_run.py::TestCmdRunWorkingDirectory::test_default_wdir_is_written tests/func/test_run.py::TestCmdRun::test_run_bad_command tests/func/test_run.py::TestRunCircularDependency::test tests/func/test_run.py::TestCmdRun::test_run_args_with_spaces tests/func/test_run.py::TestCmdRun::test_keyboard_interrupt tests/func/test_run.py::TestRunCircularDependency::test_graph tests/func/test_run.py::TestRunCircularDependency::test_outs_no_cache tests/func/test_run.py::TestCmdRunOverwrite::test tests/func/test_run.py::TestCmdRunCliMetrics::test_cached tests/func/test_run.py::TestCmdRunWorkingDirectory::test_cwd_is_ignored tests/func/test_run.py::TestRunBadCwd::test_same_prefix tests/func/test_run.py::TestRunDeterministic::test tests/func/test_run.py::TestRunDeterministicCallback::test tests/func/test_run.py::TestPersistentOutput::test_ignore_build_cache tests/func/test_run.py::TestRunDeterministicRemoveDep::test tests/func/test_run.py::TestRunBadWdir::test_not_found tests/func/test_run.py::TestRunDeterministicChangedDepsList::test tests/func/test_run.py::TestRunCircularDependency::test_non_normalized_paths tests/func/test_run.py::TestRunDeterministicNewDep::test tests/func/test_run.py::TestRunDeterministicChangedCmd::test tests/func/test_run.py::TestRunPersistOuts::test tests/func/test_run.py::TestRunStageInsideOutput::test_cwd tests/func/test_run.py::TestRunBadWdir::test_not_dir tests/func/test_run.py::TestRunDeterministicOverwrite::test tests/func/test_run.py::TestCmdRunWorkingDirectory::test_fname_changes_path_and_wdir tests/func/test_run.py::TestRunBadStageFilename::test