def __init__(self, n, time): self._n = n if time: cmd = 'time QuadraticSieve' else: cmd = 'QuadraticSieve' tmpdir() self._p = SageSpawn(cmd) cleaner.cleaner(self._p.pid, 'QuadraticSieve') self._p.sendline(str(self._n) + '\n\n\n') self._done = False self._out = '' self._time = '' self._do_time = time
def __init__(self, n, time): self._n = n if time: cmd = 'time QuadraticSieve' else: cmd = 'QuadraticSieve' env = os.environ.copy() env['TMPDIR'] = tmp_dir('qsieve') self._p = SageSpawn(cmd, env=env) cleaner.cleaner(self._p.pid, 'QuadraticSieve') self._p.sendline(str(self._n) + '\n\n\n') self._done = False self._out = '' self._time = '' self._do_time = time
def __init__(self, n, time): self._n = n if time: cmd = 'time QuadraticSieve' else: cmd = 'QuadraticSieve' tmpdir() self._p = SageSpawn(cmd) cleaner.cleaner(self._p.pid, 'QuadraticSieve') self._p.sendline(str(self._n)+'\n\n\n') self._done = False self._out = '' self._time = '' self._do_time = time
def __init__(self, n, time): self._n = n if time: cmd = 'time QuadraticSieve' else: cmd = 'QuadraticSieve' env = os.environ.copy() env['TMPDIR'] = tmp_dir('qsieve') self._p = SageSpawn(cmd, env=env) cleaner.cleaner(self._p.pid, 'QuadraticSieve') self._p.sendline(str(self._n)+'\n\n\n') self._done = False self._out = '' self._time = '' self._do_time = time
def _start(self, alt_message=None, block_during_init=True): from sage.misc.misc import sage_makedirs self.quit() # in case one is already running self._session_number += 1 if self.__logfile is None: # If the 'SAGE_PEXPECT_LOG' environment variable is set and # there is no logfile already defined, then create a # logfile in .sage/pexpect_logs/ if self.__logfilename is None and 'SAGE_PEXPECT_LOG' in os.environ: from sage.env import DOT_SAGE logs = os.path.join(DOT_SAGE, 'pexpect_logs') sage_makedirs(logs) self.__logfilename = '%s/%s-%s-%s-%s.log'%(logs, self.name(), os.getpid(), id(self), self._session_number) if self.__logfilename is not None: self.__logfile = open(self.__logfilename, 'w') cmd = self.__command if self.__verbose_start: print cmd print "Starting %s"%cmd.split()[0] if self.__remote_cleaner and self._server: c = 'sage-native-execute ssh %s "nohup sage -cleaner" &'%self._server os.system(c) # Unset some environment variables for the children to # reduce the chances they do something complicated breaking # the terminal interface. # See Trac #12221 and #13859. pexpect_env = dict(os.environ) pexpect_del_vars = ['TERM', 'COLUMNS'] for i in pexpect_del_vars: try: del pexpect_env[i] except KeyError: pass # Run child from self.__path currentdir = os.getcwd() os.chdir(self.__path) try: try: self._expect = SageSpawn(cmd, logfile=self.__logfile, timeout=None, # no timeout env=pexpect_env, name=self._repr_(), quit_string=self._quit_string()) except (ExceptionPexpect, pexpect.EOF) as e: # Change pexpect errors to RuntimeError raise RuntimeError("unable to start %s because the command %r failed: %s\n%s" % (self.name(), cmd, e, self._install_hints())) except BaseException: self._expect = None self._session_number = BAD_SESSION raise finally: os.chdir(currentdir) if self._do_cleaner(): cleaner.cleaner(self._expect.pid, cmd) self._expect.maxread = self.__maxread self._expect.delaybeforesend = 0 try: self._expect.expect(self._prompt) except (pexpect.TIMEOUT, pexpect.EOF): self._expect = None self._session_number = BAD_SESSION raise RuntimeError("unable to start %s" % self.name()) self._expect.timeout = None # Calling tcsetattr earlier exposes bugs in various pty # implementations, see :trac:`16474`. Since we haven't # **written** anything so far it is safe to wait with # switching echo off until now. if not self._terminal_echo: self._expect.setecho(0) with gc_disabled(): if block_during_init: for X in self.__init_code: self.eval(X) else: for X in self.__init_code: self._send(X)
class Expect(Interface): """ Expect interface object. """ def __init__(self, name, prompt, command=None, server=None, server_tmpdir=None, ulimit = None, maxread=100000, script_subdirectory=None, restart_on_ctrlc=False, verbose_start=False, init_code=[], max_startup_time=None, logfile = None, eval_using_file_cutoff=0, do_cleaner=True, remote_cleaner=False, path=None, terminal_echo=True): Interface.__init__(self, name) self.__is_remote = False self.__remote_cleaner = remote_cleaner if command is None: command = name if server is not None: if ulimit: command = "sage-native-execute ssh -t %s 'ulimit %s; %s'"%(server, ulimit, command) else: command = "sage-native-execute ssh -t %s '%s'"%(server, command) self.__is_remote = True # eval_using_file_cutoff = 0 # don't allow this! if verbose_start: print "Using remote server" print command self._server = server if server_tmpdir is None: # TO DO: Why default to /tmp/? Might be better to use the expect process itself to get a tmp folder print "No remote temporary directory (option server_tmpdir) specified, using /tmp/ on "+server self.__remote_tmpdir = "/tmp/" else: self.__remote_tmpdir = server_tmpdir else: self._server = None self.__do_cleaner = do_cleaner self.__maxread = maxread self._eval_using_file_cutoff = eval_using_file_cutoff self.__command = command self._prompt = prompt self._restart_on_ctrlc = restart_on_ctrlc self.__verbose_start = verbose_start if path is not None: self.__path = os.path.abspath(path) elif script_subdirectory is None: self.__path = os.getcwd() else: self.__path = os.path.join(SAGE_EXTCODE, name, script_subdirectory) if not os.path.isdir(self.__path): raise EnvironmentError("path %r does not exist" % self.__path) self.__initialized = False self.__seq = -1 self._expect = None self._session_number = 0 self.__init_code = init_code #Handle the log file if isinstance(logfile, six.string_types): self.__logfile = None self.__logfilename = logfile else: self.__logfile = logfile self.__logfilename = None quit.expect_objects.append(weakref.ref(self)) self._available_vars = [] self._terminal_echo = terminal_echo def _get(self, wait=0.1, alternate_prompt=None): if self._expect is None: self._start() E = self._expect wait = float(wait) try: if alternate_prompt is None: E.expect(self._prompt, timeout=wait) else: E.expect(alternate_prompt, timeout=wait) except pexpect.TIMEOUT: return False, E.before except pexpect.EOF: return True, E.before except Exception: # weird major problem! return True, E.before return True, E.before def _send(self, cmd): if self._expect is None: self._start() E = self._expect self.__so_far = '' E.sendline(cmd) def is_running(self): """ Return True if self is currently running. """ if self._expect is None: return False try: os.kill(self._expect.pid,0) except OSError: # This means the process is not running return False return True def _so_far(self, wait=0.1, alternate_prompt=None): """ Return whether done and output so far and new output since last time called. """ done, new = self._get(wait=wait, alternate_prompt=alternate_prompt) try: if done: #if new is not None: X = self.__so_far + new del self.__so_far return True, X, new #new = self._expect.before try: self.__so_far += new except (AttributeError, TypeError): self.__so_far = new return False, self.__so_far, new except AttributeError as msg: # no __so_far raise RuntimeError(msg) def is_remote(self): return self.__is_remote def is_local(self): return not self.__is_remote def user_dir(self): return self.__path def _change_prompt(self, prompt): self._prompt = prompt def path(self): return self.__path def expect(self): if self._expect is None: self._start() return self._expect def pid(self): """ Return the PID of the underlying sub-process. REMARK: If the interface terminates unexpectedly, the original PID will still be used. But if it was terminated using :meth:`quit`, a new sub-process with a new PID is automatically started. EXAMPLE:: sage: pid = gap.pid() sage: gap.eval('quit;') '' sage: pid == gap.pid() True sage: gap.quit() sage: pid == gap.pid() False """ if self._expect is None: self._start() return self._expect.pid def _install_hints(self): r""" Hints for installing needed slave program on your computer. There are no hints by default. """ return '' def _install_hints_ssh(self): r""" Hints for installing passwordless authentication on your computer... """ # Written by Paul-Olivier Dehaye 2007/08/23 return """ In order for Sage (on "local") to launch a "slave" process on "remote", the following command needs to work from local's console, without the need to enter any password: "******", where "slave" could be "math" (for text-mode Mathematica), "gap", "magma", "sage", "maple", etc. This thus requires passwordless authentication to be setup, which can be done with commands like these: cd; ssh-keygen -t rsa; scp .ssh/id_rsa.pub remote:.ssh/authorized_keys2\n (WARNING: this would overwrite your current list of authorized keys on "remote") In many cases, the server that can actually run "slave" is not accessible from the internet directly, but you have to hop through an intermediate trusted server, say "gate". If that is your case, get help with _install_hints_ssh_through_gate(). """ def _install_hints_ssh_through_gate(self): r""" Hints for installing passwordless authentication through a gate """ # Written by Paul-Olivier Dehaye 2007/08/23 return """ We assume you would like to run a "slave" process on a machine called "remote" from a machine running Sage called "local". We also assume "remote" can only be accessed from "local" by ssh'ing first to "gate" (this is a fairly common setup). Sometimes, "gate" and "remote" have a shared filesystem, and this helps a bit. Note: You cannot just create shell scripts on "local" and "gate" that would use two successive SSH connections to "remote" in order to simulate running "slave" locally. This is because Sage will sometimes use files (and scp) to communicate with "remote", which shell scripts would not take care of. You need to setup: * passwordless authentication to "gate" from "local" * add passwordless authentication to "remote" from "local", for instance by appending the file local:~/.ssh/id_rsa.pub to remote:~/.ssh/authorized_keys2 and logging in once (this is only needed if "remote" and "gate" don\'t share filesystem) * add a few lines to your local:~/.ssh/ssh_config. Mine look like Host remote_for_sage ProxyCommand ssh gate nc -w 1 remote 22 That's it, normally. The last step tells ssh that whenever an ssh connection is required to the host "remote_for_sage", it should tunnel it through "gate". Any attempt to scp-connect to "remote_for_sage" will use ssh and thus this configuration file, and properly channel those file transfers through the tunnel. A good test is to attempt an scp connection from the command-line of "local" to "remote_for_sage" as if no tunnel through "gate" was required. No password should be asked for the second time around. Finally, we created the new name "remote_for_sage" for "remote", but this name only exists locally. this is to avoid interfering with any other program that might already ssh to "remote" in their own way. If this all works, you can then make calls like: math = Mathematica(server="remote_for_sage") """ def _do_cleaner(self): try: return self.__do_cleaner except AttributeError: return False def _start(self, alt_message=None, block_during_init=True): from sage.misc.misc import sage_makedirs self.quit() # in case one is already running self._session_number += 1 if self.__logfile is None: # If the 'SAGE_PEXPECT_LOG' environment variable is set and # there is no logfile already defined, then create a # logfile in .sage/pexpect_logs/ if self.__logfilename is None and 'SAGE_PEXPECT_LOG' in os.environ: from sage.env import DOT_SAGE logs = os.path.join(DOT_SAGE, 'pexpect_logs') sage_makedirs(logs) self.__logfilename = '%s/%s-%s-%s-%s.log'%(logs, self.name(), os.getpid(), id(self), self._session_number) if self.__logfilename is not None: self.__logfile = open(self.__logfilename, 'w') cmd = self.__command if self.__verbose_start: print cmd print "Starting %s"%cmd.split()[0] if self.__remote_cleaner and self._server: c = 'sage-native-execute ssh %s "nohup sage -cleaner" &'%self._server os.system(c) # Unset some environment variables for the children to # reduce the chances they do something complicated breaking # the terminal interface. # See Trac #12221 and #13859. pexpect_env = dict(os.environ) pexpect_del_vars = ['TERM', 'COLUMNS'] for i in pexpect_del_vars: try: del pexpect_env[i] except KeyError: pass # Run child from self.__path currentdir = os.getcwd() os.chdir(self.__path) try: try: self._expect = SageSpawn(cmd, logfile=self.__logfile, timeout=None, # no timeout env=pexpect_env, name=self._repr_(), quit_string=self._quit_string()) except (ExceptionPexpect, pexpect.EOF) as e: # Change pexpect errors to RuntimeError raise RuntimeError("unable to start %s because the command %r failed: %s\n%s" % (self.name(), cmd, e, self._install_hints())) except BaseException: self._expect = None self._session_number = BAD_SESSION raise finally: os.chdir(currentdir) if self._do_cleaner(): cleaner.cleaner(self._expect.pid, cmd) self._expect.maxread = self.__maxread self._expect.delaybeforesend = 0 try: self._expect.expect(self._prompt) except (pexpect.TIMEOUT, pexpect.EOF): self._expect = None self._session_number = BAD_SESSION raise RuntimeError("unable to start %s" % self.name()) self._expect.timeout = None # Calling tcsetattr earlier exposes bugs in various pty # implementations, see :trac:`16474`. Since we haven't # **written** anything so far it is safe to wait with # switching echo off until now. if not self._terminal_echo: self._expect.setecho(0) with gc_disabled(): if block_during_init: for X in self.__init_code: self.eval(X) else: for X in self.__init_code: self._send(X) def clear_prompts(self): while True: try: self._expect.expect(self._prompt, timeout=0.1) except pexpect.TIMEOUT: return def _reset_expect(self): """ Delete ``self._expect`` and reset any state. This is called by :meth:`quit` and :meth:`detach`. EXAMPLES:: sage: gp("eulerphi(49)") 42 sage: print gp._expect PARI/GP interpreter with PID ... sage: gp._reset_expect() sage: print gp._expect None sage: gp("eulerphi(49)") 42 """ self._session_number += 1 try: del self.__local_tmpfile except AttributeError: pass self._expect = None def quit(self, verbose=False, timeout=None): """ Quit the running subprocess. INPUT: - ``verbose`` -- (boolean, default ``False``) print a message when quitting this process? EXAMPLES:: sage: a = maxima('y') sage: maxima.quit(verbose=True) Exiting Maxima with PID ... running .../local/bin/maxima ... sage: a._check_valid() Traceback (most recent call last): ... ValueError: The maxima session in which this object was defined is no longer running. Calling ``quit()`` a second time does nothing:: sage: maxima.quit(verbose=True) """ if timeout is not None: from sage.misc.superseded import deprecation deprecation(17686, 'the timeout argument to quit() is deprecated and ignored') if self._expect is not None: if verbose: if self.is_remote(): print "Exiting %r (running on %s)"%(self._expect, self._server) else: print "Exiting %r"%(self._expect,) self._expect.close() self._reset_expect() def detach(self): """ Forget the running subprocess: keep it running but pretend that it's no longer running. EXAMPLES:: sage: a = maxima('y') sage: saved_expect = maxima._expect # Save this to close later sage: maxima.detach() sage: a._check_valid() Traceback (most recent call last): ... ValueError: The maxima session in which this object was defined is no longer running. sage: saved_expect.close() # Close child process Calling ``detach()`` a second time does nothing:: sage: maxima.detach() """ try: self._expect._keep_alive() except AttributeError: pass self._reset_expect() def _quit_string(self): """ Return the string which will be used to quit the application. EXAMPLES:: sage: gp._quit_string() '\\q' sage: maxima._quit_string() 'quit();' """ return 'quit' def _send_interrupt(self): """ Send an interrupt to the application. This is used internally by :meth:`interrupt`. First CTRL-C to stop the current command, then quit. """ self._expect.sendline(chr(3)) self._expect.sendline(self._quit_string()) def _local_tmpfile(self): """ Return a filename that is used to buffer long command lines for this interface INPUT: ``e`` -- an expect interface instance OUTPUT: A string that provides a temporary filename and is unique for the given interface. TEST: The filename is cached:: sage: gap._local_tmpfile() is gap._local_tmpfile() True The following two problems were fixed in :trac:`10004`. 1. Different interfaces have different temp-files:: sage: gap._local_tmpfile() != singular._local_tmpfile() True 2. Interface instances in different branches of a parallelised function have different temp-files:: sage: @parallel ....: def f(n): ....: return gap._local_tmpfile() sage: L = [t[1] for t in f(range(5))] sage: len(set(L)) 5 The following used to fail:: sage: s = gap._local_tmpfile() sage: L = [t[1] for t in f(range(5))] sage: len(set(L)) 5 AUTHOR: - Simon King (2010-09): Making the tmp-file unique for the interface instance """ try: return self.__local_tmpfile except AttributeError: self.__local_tmpfile = os.path.join(SAGE_TMP_INTERFACE, 'tmp' + str(self.pid())) return self.__local_tmpfile def _remote_tmpdir(self): return self.__remote_tmpdir def _remote_tmpfile(self): try: return self.__remote_tmpfile except AttributeError: self.__remote_tmpfile = self._remote_tmpdir()+"/interface_%s:%s"%(LOCAL_IDENTIFIER,self.pid()) return self.__remote_tmpfile def _send_tmpfile_to_server(self, local_file=None, remote_file=None): if local_file is None: local_file = self._local_tmpfile() if remote_file is None: remote_file = self._remote_tmpfile() cmd = 'scp "%s" %s:"%s" 1>&2 2>/dev/null'%(local_file, self._server, remote_file) # print cmd os.system(cmd) def _get_tmpfile_from_server(self, local_file=None,remote_file=None): if local_file is None: local_file = self._local_tmpfile() if remote_file is None: remote_file = self._remote_tmpfile() cmd = 'scp %s:"%s" "%s" 1>&2 2>/dev/null'%( self._server, remote_file, local_file) # print cmd os.system(cmd) def _remove_tmpfile_from_server(self): if not (self.__remote_tmpfile is None): raise NotImplementedError def _eval_line_using_file(self, line, restart_if_needed=True): """ Evaluate a line of commands, using a temporary file. REMARK: By default, this is called when a long command is evaluated in :meth:`eval`. If the command can not be evaluated since the interface has crashed, it is automatically restarted and tried again *once*. INPUT: - ``line`` -- (string) a command. - ``restart_if_needed`` - (optional bool, default ``True``) -- If it is ``True``, the command evaluation is evaluated a second time after restarting the interface, if an ``EOFError`` occured. TESTS:: sage: singular._eval_line_using_file('def a=3;') '' sage: singular('a') 3 sage: singular.eval('quit;') '' sage: singular._eval_line_using_file('def a=3;') Singular crashed -- automatically restarting. '' sage: singular('a') 3 sage: singular.eval('quit;') '' sage: singular._eval_line_using_file('def a=3;', restart_if_needed=False) Traceback (most recent call last): ... RuntimeError: Singular terminated unexpectedly while reading in a large line... We end by triggering a re-start of Singular, since otherwise the doc test of another method would fail by a side effect. :: sage: singular(3) Singular crashed -- automatically restarting. 3 """ F = open(self._local_tmpfile(), 'w') F.write(line+'\n') F.close() tmp_to_use = self._local_tmpfile() if self.is_remote(): self._send_tmpfile_to_server() tmp_to_use = self._remote_tmpfile() try: s = self._eval_line(self._read_in_file_command(tmp_to_use), allow_use_file=False, restart_if_needed=False) except pexpect.EOF as msg: if self._quit_string() in line: # we expect to get an EOF if we're quitting. return '' elif restart_if_needed==True: # the subprocess might have crashed try: self._synchronize() return self._post_process_from_file(self._eval_line_using_file(line, restart_if_needed=False)) except RuntimeError as msg: raise RuntimeError('%s terminated unexpectedly while reading in a large line:\n%s'%(self,msg[0])) except TypeError: pass raise RuntimeError('%s terminated unexpectedly while reading in a large line'%self) except RuntimeError as msg: if self._quit_string() in line: if self._expect is None or not self._expect.isalive(): return '' raise if restart_if_needed==True and (self._expect is None or not self._expect.isalive()): try: self._synchronize() return self._post_process_from_file(self._eval_line_using_file(line, restart_if_needed=False)) except TypeError: pass except RuntimeError as msg: raise RuntimeError('%s terminated unexpectedly while reading in a large line'%self) if "Input/output error" in msg[0]: # This occurs on non-linux machines raise RuntimeError('%s terminated unexpectedly while reading in a large line'%self) raise RuntimeError('%s terminated unexpectedly while reading in a large line:\n%s'%(self,msg[0])) return self._post_process_from_file(s) def _post_process_from_file(self, s): return s def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if_needed=True): """ Evaluate a line of commands. REMARK: By default, a long command (length exceeding ``self._eval_using_file_cutoff``) is evaluated using :meth:`_eval_line_using_file`. If the command can not be evaluated since the interface has crashed, it is automatically restarted and tried again *once*. If the optional ``wait_for_prompt`` is ``False`` then even a very long line will not be evaluated by :meth:`_eval_line_using_file`, since this does not support the ``wait_for_prompt`` option. INPUT: - ``line`` -- (string) a command. - ``allow_use_file`` (optional bool, default ``True``) -- allow to evaluate long commands using :meth:`_eval_line_using_file`. - ``wait_for_prompt`` (optional bool, default ``True``) -- wait until the prompt appears in the sub-process' output. - ``restart_if_needed`` (optional bool, default ``True``) -- If it is ``True``, the command evaluation is evaluated a second time after restarting the interface, if an ``EOFError`` occured. TESTS:: sage: singular._eval_line('def a=3;') '' sage: singular('a') 3 sage: singular.eval('quit;') '' sage: singular._eval_line('def a=3;') Singular crashed -- automatically restarting. '' sage: singular('a') 3 sage: singular.eval('kill a') '' We are now sending a command that would run forever. But since we declare that we are not waiting for a prompt, we can interrupt it without a KeyboardInterrupt. At the same time, we test that the line is not forwarded to :meth:`_eval_line_using_file`, since that method would not support the ``wait_for_prompt`` option. For reasons which are currently not understood, the ``interrupt`` test usually returns immediately, but sometimes it takes a very long time on the same system. :: sage: cutoff = singular._eval_using_file_cutoff sage: singular._eval_using_file_cutoff = 4 sage: singular._eval_line('for(int i=1;i<=3;i++){i=1;};', wait_for_prompt=False) '' sage: singular.interrupt() True sage: singular._eval_using_file_cutoff = cutoff The interface still works after this interrupt:: sage: singular('2+3') 5 Last, we demonstrate that by default the execution of a command is tried twice if it fails the first time due to a crashed interface:: sage: singular.eval('quit;') '' sage: singular._eval_line_using_file('def a=3;', restart_if_needed=False) Traceback (most recent call last): ... RuntimeError: Singular terminated unexpectedly while reading in a large line... Since the test of the next method would fail, we re-start Singular now. :: sage: singular('2+3') Singular crashed -- automatically restarting. 5 """ if allow_use_file and wait_for_prompt and self._eval_using_file_cutoff and len(line) > self._eval_using_file_cutoff: return self._eval_line_using_file(line) try: if self._expect is None: self._start() E = self._expect try: if len(line) >= 4096: raise RuntimeError("Sending more than 4096 characters with %s on a line may cause a hang and you're sending %s characters"%(self, len(line))) E.sendline(line) if wait_for_prompt == False: return '' except OSError as msg: if restart_if_needed: # The subprocess most likely crashed. # If it's really still alive, we fall through # and raise RuntimeError. if sys.platform.startswith('sunos'): # On (Open)Solaris, we might need to wait a # while because the process might not die # immediately. See Trac #14371. for t in [0.5, 1.0, 2.0]: if E.isalive(): time.sleep(t) else: break if not E.isalive(): try: self._synchronize() except (TypeError, RuntimeError): pass return self._eval_line(line,allow_use_file=allow_use_file, wait_for_prompt=wait_for_prompt, restart_if_needed=False) raise RuntimeError, "%s\nError evaluating %s in %s"%(msg, line, self), sys.exc_info()[2] if len(line)>0: try: if isinstance(wait_for_prompt, six.string_types): E.expect(wait_for_prompt) else: E.expect(self._prompt) except pexpect.EOF as msg: try: if self.is_local(): tmp_to_use = self._local_tmpfile() else: tmp_to_use = self._remote_tmpfile() if self._read_in_file_command(tmp_to_use) in line: raise pexpect.EOF(msg) except NotImplementedError: pass if self._quit_string() in line: # we expect to get an EOF if we're quitting. return '' elif restart_if_needed==True: # the subprocess might have crashed try: self._synchronize() return self._eval_line(line,allow_use_file=allow_use_file, wait_for_prompt=wait_for_prompt, restart_if_needed=False) except (TypeError, RuntimeError): pass raise RuntimeError("%s\n%s crashed executing %s"%(msg,self, line)) if self._terminal_echo: out = E.before else: out = E.before.rstrip('\n\r') else: if self._terminal_echo: out = '\n\r' else: out = '' except KeyboardInterrupt: self._keyboard_interrupt() raise KeyboardInterrupt("Ctrl-c pressed while running %s"%self) if self._terminal_echo: i = out.find("\n") j = out.rfind("\r") return out[i+1:j].replace('\r\n','\n') else: return out.replace('\r\n','\n') def _keyboard_interrupt(self): print "Interrupting %s..."%self if self._restart_on_ctrlc: try: self._expect.close(force=1) except pexpect.ExceptionPexpect as msg: raise pexpect.ExceptionPexpect( "THIS IS A BUG -- PLEASE REPORT. This should never happen.\n" + msg) self._start() raise KeyboardInterrupt("Restarting %s (WARNING: all variables defined in previous session are now invalid)"%self) else: self._expect.sendline(chr(3)) # send ctrl-c self._expect.expect(self._prompt) self._expect.expect(self._prompt) raise KeyboardInterrupt("Ctrl-c pressed while running %s"%self) def interrupt(self, tries=5, timeout=2.0, quit_on_fail=True): E = self._expect if E is None: return True try: for i in range(tries): self._send_interrupt() try: E.expect(self._prompt, timeout=timeout) except (pexpect.TIMEOUT, pexpect.EOF): pass else: return True # Success except Exception: pass # Failed to interrupt... if quit_on_fail: self.quit() return False ########################################################################### # BEGIN Synchronization code. ########################################################################### def _before(self): r""" Return the previous string that was sent through the interface. EXAMPLES:: sage: singular('2+3') 5 sage: singular._before() '5\r\n' """ return self._expect.before def _interrupt(self): for i in range(15): try: self._sendstr('quit;\n'+chr(3)) self._expect_expr(timeout=2) except pexpect.TIMEOUT: pass except pexpect.EOF: self._crash_msg() self.quit() else: return def _expect_expr(self, expr=None, timeout=None): r""" Wait for a given expression expr (which could be a regular expression or list of regular expressions) to appear in the output for at most timeout seconds. Use ``r._expect.before`` to see what was put in the output stream before the expression. INPUT: - ``expr`` - None or a string or list of strings (default: None) - ``timeout`` - None or a number (default: None) EXAMPLES: We test all of this using the R interface. First we put 10 + 15 in the input stream:: sage: r._sendstr('abc <- 10 +15;\n') Here an exception is raised because 25 hasn't appeared yet in the output stream. The key thing is that this doesn't lock, but instead quickly raises an exception. :: sage: t = walltime() sage: try: ....: r._expect_expr('25', timeout=0.5) ....: except Exception: ....: print 'Did not get expression' Did not get expression A quick consistency check on the time that the above took:: sage: w = walltime(t); w > 0.4 and w < 10 True We tell R to print abc, which equals 25. :: sage: r._sendstr('abc;\n') Now 25 is in the output stream, so we can wait for it. :: sage: r._expect_expr('25') This gives us everything before the 25. :: sage: r._expect.before 'abc;\r\n[1] ' We test interrupting ``_expect_expr`` using the GP interface, see #6661. Unfortunately, this test doesn't work reliably using Singular, see #9163 and the follow-up #10476. The ``gp.eval('0')`` in this test makes sure that ``gp`` is running, so a timeout of 1 second should be sufficient. :: sage: print sage0.eval("dummy=gp.eval('0'); alarm(1); gp._expect_expr('1')") # long time Control-C pressed. Interrupting PARI/GP interpreter. Please wait a few seconds... ... AlarmInterrupt: """ if expr is None: # the following works around gap._prompt_wait not being defined expr = getattr(self, '_prompt_wait', None) or self._prompt if self._expect is None: self._start() try: if timeout: i = self._expect.expect(expr, timeout=timeout) else: i = self._expect.expect(expr) if i > 0: v = self._expect.before self.quit() raise ValueError("%s\nComputation failed due to a bug in %s -- NOTE: Had to restart."%(v, self)) except KeyboardInterrupt: print("Control-C pressed. Interrupting %s. Please wait a few seconds..."%self) self.interrupt() raise def _sendstr(self, str): r""" Send a string to the pexpect interface, autorestarting the expect interface if anything goes wrong. INPUT: - ``str`` - a string EXAMPLES: We illustrate this function using the R interface:: sage: r._sendstr('a <- 10;\n') sage: r.eval('a') '[1] 10' We illustrate using the singular interface:: sage: singular._sendstr('int i = 5;') sage: singular('i') 5 """ if self._expect is None: self._start() try: os.write(self._expect.child_fd, str) except OSError: self._crash_msg() self.quit() self._sendstr(str) def _crash_msg(self): r""" Show a message if the interface crashed. EXAMPLE:: sage: singular._crash_msg() Singular crashed -- automatically restarting. :: sage: singular('2+3') 5 sage: singular._sendstr('quit;\n') # make it so that singular appears to die. sage: singular('2+3') Singular crashed -- automatically restarting. 5 """ print "%s crashed -- automatically restarting."%self def _synchronize(self, cmd='1+%s;\n'): """ Synchronize pexpect interface. This put a random integer (plus one!) into the output stream, then waits for it, thus resynchronizing the stream. If the random integer doesn't appear within 1 second, the interface is sent interrupt signals. This way, even if you somehow left the interface in a busy state computing, calling _synchronize gets everything fixed. EXAMPLES: We observe nothing, just as it should be:: sage: r._synchronize() TESTS: This illustrates a synchronization bug being fixed (thanks to Simon King and David Joyner for tracking this down):: sage: R.<x> = QQ[]; f = x^3 + x + 1; g = x^3 - x - 1; r = f.resultant(g); gap(ZZ); singular(R) Integers // characteristic : 0 // number of vars : 1 // block 1 : ordering lp // : names x // block 2 : ordering C """ if self._expect is None: return rnd = randrange(2147483647) s = str(rnd+1) cmd = cmd%rnd self._sendstr(cmd) try: self._expect_expr(timeout=0.5) if not s in self._expect.before: self._expect_expr(s,timeout=0.5) self._expect_expr(timeout=0.5) except pexpect.TIMEOUT: self._interrupt() except pexpect.EOF: self._crash_msg() self.quit() ########################################################################### # END Synchronization code. ########################################################################### def eval(self, code, strip=True, synchronize=False, locals=None, allow_use_file=True, split_lines="nofile", **kwds): """ INPUT: - ``code`` -- text to evaluate - ``strip`` -- bool; whether to strip output prompts, etc. (ignored in the base class). - ``locals`` -- None (ignored); this is used for compatibility with the Sage notebook's generic system interface. - ``allow_use_file`` -- bool (default: True); if True and ``code`` exceeds an interface-specific threshold then ``code`` will be communicated via a temporary file rather that the character-based interface. If False then the code will be communicated via the character interface. - ``split_lines`` -- Tri-state (default: "nofile"); if "nofile" then ``code`` is sent line by line unless it gets communicated via a temporary file. If True then ``code`` is sent line by line, but some lines individually might be sent via temporary file. Depending on the interface, this may transform grammatical ``code`` into ungrammatical input. If False, then the whole block of code is evaluated all at once. - ``**kwds`` -- All other arguments are passed onto the _eval_line method. An often useful example is reformat=False. """ if synchronize: try: self._synchronize() except AttributeError: pass if strip: try: code = self._strip_prompt(code) except AttributeError: pass if not isinstance(code, six.string_types): raise TypeError('input code must be a string.') #Remove extra whitespace code = code.strip() try: with gc_disabled(): if (split_lines == "nofile" and allow_use_file and self._eval_using_file_cutoff and len(code) > self._eval_using_file_cutoff): return self._eval_line_using_file(code) elif split_lines: return '\n'.join([self._eval_line(L, allow_use_file=allow_use_file, **kwds) for L in code.split('\n') if L != '']) else: return self._eval_line(code, allow_use_file=allow_use_file, **kwds) # DO NOT CATCH KeyboardInterrupt, as it is being caught # by _eval_line # In particular, do NOT call self._keyboard_interrupt() except TypeError as s: raise TypeError('error evaluating "%s":\n%s'%(code,s)) ############################################################ # Functions for working with variables. # The first three must be overloaded by derived classes, # and the definition depends a lot on the class. But # the functionality one gets from this is very nice. ############################################################ def _object_class(self): """ EXAMPLES:: sage: from sage.interfaces.expect import Expect sage: Expect._object_class(maxima) <class 'sage.interfaces.expect.ExpectElement'> """ return ExpectElement def _function_class(self): """ EXAMPLES:: sage: from sage.interfaces.expect import Expect sage: Expect._function_class(maxima) <class 'sage.interfaces.expect.ExpectFunction'> """ return ExpectFunction def _function_element_class(self): """ EXAMPLES:: sage: from sage.interfaces.expect import Expect sage: Expect._function_element_class(maxima) <class 'sage.interfaces.expect.FunctionElement'> """ return FunctionElement
class qsieve_nonblock: """ A non-blocking version of Hart's quadratic sieve. The sieve starts running when you create the object, but you can still use Sage in parallel. EXAMPLES:: sage: k = 19; n = next_prime(10^k)*next_prime(10^(k+1)) sage: q = qsieve(n, block=False, time=True) # optional - time sage: q # random output; optional - time Proper factors so far: [] sage: q # random output; optional - time ([10000000000000000051, 100000000000000000039], '0.21') sage: q.list() # random output; optional - time [10000000000000000051, 100000000000000000039] sage: q.time() # random output; optional - time '0.21' sage: q = qsieve(next_prime(10^20)*next_prime(10^21), block=False) sage: q # random output Proper factors so far: [100000000000000000039, 1000000000000000000117] sage: q # random output [100000000000000000039, 1000000000000000000117] """ def __init__(self, n, time): self._n = n if time: cmd = 'time QuadraticSieve' else: cmd = 'QuadraticSieve' tmpdir() self._p = SageSpawn(cmd) cleaner.cleaner(self._p.pid, 'QuadraticSieve') self._p.sendline(str(self._n) + '\n\n\n') self._done = False self._out = '' self._time = '' self._do_time = time def n(self): """ Return the integer that is being factored. """ return self._n def pid(self): """ Return the PIN id of the QuadraticSieve process (actually of the time process that spawns the sieve process). """ return self._p.pid def done(self): """ Return True if the sieve process has completed. """ return self._done def __repr__(self): """ Return a text representation of self. """ if self._done: if hasattr(self, '_killed') and self._killed: return "Factorization was terminated early." v = data_to_list(self._get(), self._n, self._do_time) if self._do_time: return str(v[:2]) else: return str(v[0]) else: return 'Proper factors so far: %s' % self.list() def cputime(self): """ Return the time in seconds (as a string) that it took to factor n, or return '?' if the factorization has not completed or the time is unknown. """ if not self._do_time: raise ValueError( "you have to start the sieve with the option time=True in order to get timing information" ) try: return data_to_list(self._get(), self._n, self._do_time)[1] except IndexError: return '?' time = cputime def log(self): """ Return all output of running the sieve so far. """ return self._get() def __getitem__(self, i): """ Return the i-th factor (in sorted order) found so far. """ return self.list()[i] def __len__(self): """ Return the number of factors found so far. If q is the Sieve object, type len(q) to see the number of factors. """ return len(self.list()) def list(self): """ Return a list of the factors found so far, as Sage integers. """ try: return data_to_list(self._get(), self._n, self._do_time)[0] except IndexError: return [] def quit(self): """ Terminate the QuadraticSieve process, in case you want to give up on computing this factorization. EXAMPLES:: sage: n = next_prime(2^310)*next_prime(2^300) sage: qs = qsieve(n, block=False) sage: qs Proper factors so far: [] sage: qs.quit() sage: qs Factorization was terminated early. """ pid = self.pid() os.killpg(int(pid), 9) #self._p.close() self._killed = True self._done = True def _get(self, timeout=0.1): """ Used internally to get information about what has been computed so far. """ if self._done: return self._out e = self._p try: e.expect('xxx', timeout=timeout) except pexpect.TIMEOUT: pass except pexpect.EOF: pass self._done = True self._p.close() self._out += e.before return self._out
class qsieve_nonblock: """ A non-blocking version of Hart's quadratic sieve. The sieve starts running when you create the object, but you can still use Sage in parallel. EXAMPLES:: sage: k = 19; n = next_prime(10^k)*next_prime(10^(k+1)) sage: q = qsieve(n, block=False, time=True) # optional - time sage: q # random output; optional - time Proper factors so far: [] sage: q # random output; optional - time ([10000000000000000051, 100000000000000000039], '0.21') sage: q.list() # random output; optional - time [10000000000000000051, 100000000000000000039] sage: q.time() # random output; optional - time '0.21' sage: q = qsieve(next_prime(10^20)*next_prime(10^21), block=False) sage: q # random output Proper factors so far: [100000000000000000039, 1000000000000000000117] sage: q # random output [100000000000000000039, 1000000000000000000117] """ def __init__(self, n, time): self._n = n if time: cmd = 'time QuadraticSieve' else: cmd = 'QuadraticSieve' tmpdir() self._p = SageSpawn(cmd) cleaner.cleaner(self._p.pid, 'QuadraticSieve') self._p.sendline(str(self._n)+'\n\n\n') self._done = False self._out = '' self._time = '' self._do_time = time def n(self): """ Return the integer that is being factored. """ return self._n def pid(self): """ Return the PIN id of the QuadraticSieve process (actually of the time process that spawns the sieve process). """ return self._p.pid def done(self): """ Return True if the sieve process has completed. """ return self._done def __repr__(self): """ Return a text representation of self. """ if self._done: if hasattr(self, '_killed') and self._killed: return "Factorization was terminated early." v = data_to_list(self._get(), self._n, self._do_time) if self._do_time: return str(v[:2]) else: return str(v[0]) else: return 'Proper factors so far: %s'%self.list() def cputime(self): """ Return the time in seconds (as a string) that it took to factor n, or return '?' if the factorization has not completed or the time is unknown. """ if not self._do_time: raise ValueError("you have to start the sieve with the option time=True in order to get timing information") try: return data_to_list(self._get(), self._n, self._do_time)[1] except IndexError: return '?' time = cputime def log(self): """ Return all output of running the sieve so far. """ return self._get() def __getitem__(self, i): """ Return the i-th factor (in sorted order) found so far. """ return self.list()[i] def __len__(self): """ Return the number of factors found so far. If q is the Sieve object, type len(q) to see the number of factors. """ return len(self.list()) def list(self): """ Return a list of the factors found so far, as Sage integers. """ try: return data_to_list(self._get(), self._n, self._do_time)[0] except IndexError: return [] def quit(self): """ Terminate the QuadraticSieve process, in case you want to give up on computing this factorization. EXAMPLES:: sage: n = next_prime(2^310)*next_prime(2^300) sage: qs = qsieve(n, block=False) sage: qs Proper factors so far: [] sage: qs.quit() sage: qs Factorization was terminated early. """ pid = self.pid() os.killpg(int(pid),9) #self._p.close() self._killed = True self._done = True def _get(self, timeout=0.1): """ Used internally to get information about what has been computed so far. """ if self._done: return self._out e = self._p try: e.expect('xxx', timeout=timeout) except pexpect.TIMEOUT: pass except pexpect.EOF: pass self._done = True self._p.close() self._out += e.before return self._out