Sublime Forum

Get output of run_command

#1

I am trying to get output of run_command, the return part or the print part and store it in a variable.

class PhpLinterCommand(sublime_plugin.TextCommand):
    def run(self, edit, saveIt=True):
        view = self.view
        print('This output to be taken and stored in variable or the one beloe return True')
        return True

class otherCommand(sublime_plugin.TextCommand):
    def run(self, edit):
       myvar = self.view.run_command('php_linter')
       print(myvar) # shuold output one of them.
0 Likes

#2

run_command() has no return. If you must pass variables among commands, you can make global variables or put them in view settings.

0 Likes

#3

LIKE:

globalVar = ''
class PhpLinterCommand(sublime_plugin.TextCommand):
    def run(self, edit, saveIt=True):
        view = self.view
        print('This output to be taken and stored in variable or the one beloe return True')
        globalVar = True

class otherCommand(sublime_plugin.TextCommand):
    def run(self, edit):
      self.view.run_command('php_linter')
       print(globalVar) # output True
0 Likes

#4

if you try to modify it, you will need to add global globalVar.

class PhpLinterCommand(sublime_plugin.TextCommand):
    def run(self, edit, saveIt=True):
        global globalVar 
        view = self.view
        print('This output to be taken and stored in variable or the one beloe return True')
        globalVar = True
1 Like

#5

The problem with using a global var is that:

  1. it’s not thread safe
  2. you need a mechanism to wait for var to be set (possibly with a timeout), and reset it when you’re done with it

A possible way to solve this is by using a queue.Queue() object. Not tested:

import queue

TIMEOUT = 2
pipe = queue.Queue(maxsize=1)

class PhpLinterCommand(sublime_plugin.TextCommand):
    def run(self, edit, saveIt=True):
        view = self.view
        myvar = "some value"
        pipe.put(myvar, timeout=TIMEOUT)

class otherCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.run_command('php_linter')
        myvar = pipe.get(timeout=TIMEOUT)
        print(myvar)  # will print "some value"
0 Likes

#6

Global variables in python are entirely thread safe. Commands are also blocking, so there’s nothing to wait for.

0 Likes

#7

Global variables in python are entirely thread safe.

Sorry to disagree but this is true only if the global var is read-only, meaning you only read its value but never change it, or change it but only from one thread. The moment you change its value from 2 threads running in parallel you’re no longer thread safe, unless you use a synchronization mechanism (threading.Lock, queue.Queue and others).

I do agree though that if you use run_command(), set_timeout() and async_set_timeout() there should never be be 2 commands / threads running in parallel (they run serially, one after another), so you should be fine. You have a problem only if you use threading.Thread() directly (I do in some of my plugins).

Commands are also blocking, so there’s nothing to wait for.

I wanted to test this assertion and verified this is indeed true:

import time
import functools
import sublime
import sublime_plugin

class MyTestCommand(sublime_plugin.WindowCommand):
    def run(self, **kwargs):
        time.sleep(0.1)
        print("result", kwargs)


class MyDummyCommand(sublime_plugin.WindowCommand):
    def run(self):
        for x in range(10):
            fun = functools.partial(
                self.window.run_command, "my_test", {"foo": x}
            )
            t = time.time()
            sublime.set_timeout_async(fun, 0)
            print("run_command returned in %.4f secs" % (time.time() - t))

Prints:

result {'foo': 0}
run_command returned in 0.1031 secs
result {'foo': 1}
run_command returned in 0.1034 secs
result {'foo': 2}
run_command returned in 0.1036 secs
result {'foo': 3}
run_command returned in 0.1039 secs
result {'foo': 4}
run_command returned in 0.1051 secs
result {'foo': 5}
run_command returned in 0.1029 secs
result {'foo': 6}
run_command returned in 0.1026 secs
result {'foo': 7}
run_command returned in 0.1035 secs
result {'foo': 8}
run_command returned in 0.1035 secs
result {'foo': 9}
run_command returned in 0.1037 secs

If you use set_timeout_async() though, run_command() returns immediately, so in that case I guess you’d still need a wait mechanism like the queue.Queue example I pasted above.

0 Likes

#8

The moment you change its value from 2 threads running in parallel you’re no longer thread safe, unless you use a synchronization mechanism ( threading.Lock , queue.Queue and others).

2 threads can’t run in parallel, there’s a global interpreter lock. The synchronization is inherent to Python.

0 Likes

#9

GIL makes a thread can be paused at any opcode. So it’s thread-safe only if your operation is atomic. Also note that operation like += may not be atomic until py3.10.

0 Likes

#10

True, Python threads run on 1 CPU and there’s a GIL, but GIL doesn’t mean that thread safety or synchronization are guaranteed.

A single CPU switches from one thread to another based on https://docs.python.org/3/library/sys.html#sys.setswitchinterval. During these quick context switches you may have:

  • thread #1 executing function foo() which uses GLOBAL_VAR at line 3 and line 6
  • thread #2 which changes the value of GLOBAL_VAR while thread #1 execution is still between line 3 and line 6

Here’s the inconsistent state. GIL won’t help you here. You need to use locks. Basically every time you use “global” keyword and add threads to the mix you have a bug.

1 Like

#11

All of that’s true. It doesn’t apply to the plugin here since the global variable is being set atomically - that doesn’t make python free from all concurrency bugs, but the worst ones are completely avoided.

0 Likes

#12

At the risk of being pedantic: it’s atomic (EDIT: it’s safe) not because setting a global is atomic or thread-safe per se. It’s not. global is sort of considered bad practice in general, and flat out a bad choice if you want to share state between threads unless you use a lock.

The reason why using global in the example above is safe is solely because of run_command(), not because of GIL or because global is atomic. As you rightly pointed out, run_command() runs serially (aka not in parallel), and waits for command completion. That is what makes the code above safe. There is only one thread at play.

Hope you don’t mind me further pointing this out. :slight_smile:

0 Likes

#13

If we’re being pedantic: Setting a variable is atomic, it’s a single bytecode instruction.

0 Likes

#14

Yes, but that doesn’t make it thread safe if 2 threads access GLOBAL_VAR without a lock. That’s my point. :slight_smile:

Nor the GIL makes any of this thread safe, as I seem you implied earlier.

0 Likes