hgmI don't think this is a correct description of UCI as it is in common use.
The official specs do not stipulate that a client cannot send anything after
sending "stop" and before receiving "bestmove". Some existing clients in fact
send "stop", "position", and "go ponder" simultaneously. The only thing that
is forbidden is send other stuff to a searching engine before you send "stop".
Similarly for "isready" / "readyok", except that there it is even allowed
to send it to a searching engine. The "isready" command exists purely for the
convenience of the client, for probing whether the engine is done processing all
preceding commands (where a "go" command is considered processed after launching
the search); the engine doesn't derive any rights from it (and in particular not
the right to receive no input).
Your state diagram also ignores that "go" is illegal without a preceding
"position". It would be better to split the "idle" state into "idle" and
"loaded", where "position" would cause the transition between the two.
expo
The "isready" command exists purely for the convenience of the client; [...]
the engine doesn't derive any rights from it (and in particular not the right
to receive no input).
The problem I see with HMK is that it leaves so many questions unanswered
(which I've always found to be rather frustrating). Most of the ambiguity
has to do with mixing synchronous and asynchronous interaction. Here a few
questions, for example:
• What should happen when the client sends "isready\n" twice in a single write,
or twice before the engine has a chance to read the pipe? is one "readyok"
sufficient, or does the engine need to send two replies? (Perhaps the
underlying question is, are some messages meant to be handled sequentially,
in a blocking manner, and some messages are meant to be handled as soon as
they are read, skipping ahead of "sequential" messages?)
• HMK states that "isready" is used both "to wait for the engine to be ready
again" and "to ping the engine to find out if it is still alive". When is
it the former and when is it the latter? If the client sends "stop" and then
"isready" in immediate succession, and the engine is still calculating, once
the engine reads the message should it wait to answer with "readyok" until
after it has stopped (because "isready" was sent after "stop") or immediately
(because "when the engine is calculating [...] the engine should immediately
answer with "readyok" without stopping the search")?
• Can the engine assume that the client will never send "setoption" while it is
calculating? That seems like a reasonable assumption, but HMK doesn't mention
it. If it does (suppose the client sends "stop" and "setoption" in immediate
succession, so that the engine recieves "setoption" while it is still
calculating, before it has had time to stop, or perhaps the client simply
sends "setoption" in the middle of a search), should the engine ignore the
"setoption" or apply the change once it has stopped?
• HMK does not require the client to send "isready" before a search (only once
at the beginning to "wait for the engine to finish initializing"). HMK also
states that "if the engine receives a command which is not supposed to come,
for example "stop" when the engine is not calculating, it should also just
ignore it." By the same token, one might expect that the engine should ignore
"go" if it receives "go" during a search. If the engine recieves a go message
while it is still calculating (because the client sent "stop" and "go" in
immediate succession and the engine read the go message before it had halted),
should it ignore the "go" message? Or should it queue up an indefinite number
of go messages? Or just one? (Perhaps the underlying question is, for the
purposes of UCI, is the engine's current state defined by client messages
(either defined by client messages being sent or defined by client messages
being read), or defined by both client and engine messages, or is the engine's
state defined by unobservable, internal variables?)
And I generally tried to resolve these sorts of questions in the simplest
way I could think of. Admittedly, there was the intentional bias I mentioned:
I generally picked solutions that simplified the implementation of engines.
I had tried modeling the interaction between client and engine as a collection
of events (client writes to a pipe, engine writes to a pipe, clients reads from
a pipe, engine reads from a pipe) and happens-before relationships, but the
diagram got rather ugly rather quickly.
I usually think of message-passing between the client and engine as happening
more or less instantaneously, but that's not actually the case, so the current
model used by in draft was an attempt to present the client-engine system as
having a single, coherent global state (rather than a disjoint pair of client
and engine states) while still being consistent and technically correct.
There is probably a better way to model it, though.
Some existing clients in fact send "stop", "position", and "go ponder"
simultaneously.
This had crossed my mind, and one of the outstanding issues is the question,
"Should the client be allowed to queue up messages in the halt state?" I'm not
entirely sure how to reconcile this with the current model, where transitions
occur when messages are sent, which is perhaps another indication that it needs
some amount of rethinking.
And I still need to get around to incorporating pondering.
Your state diagram also ignores that "go" is illegal without a preceding
"position".
This was one of the intentional changes, meant primarily to simplify the
state transition model (and secondarily to reduce the surface area of undefined
behavior). In such cases, I generally tried to match the behavior of common
engines (Stockfish and Komodo, for example).
Ras
the question, "Should the client be allowed to queue up messages in the halt
state?"
I do that because the client isn't allowed to send messages other than "stop"
and "isready" during search anyway. The problem with discarding messages
engine-side is that it opens bugs of wrongly discarding. I've seen a race
condition in a number of engines with dedicated input thread and discarding
that goes like this, usually when you run a tournament using all logical cores:
• engine sends "bestmove"
• engine gets scheduled away
• client sends next command
• engine input thread gets scheduled
• engine input thread discards command
• engine search (or main, or whatever) thread gets scheduled and sets the
"stop ignoring messages" flag for the input thread
The result: if the engine ignores both the "position" and the "go" command,
it will hang and lose on time while still reacting to "isready". If only the
position command is ignored, but the flag setting arrives in time to process
the go command, the engine calculates on the wrong position, i.e. the one from
the turn before, and may put out a nonsensical or even illegal next bestmove.
The quick and dirty fix is to first set the "accept messages" flag for the
input thread and then printing the best move, but at that point, it's another
race condition that would hit in if the client were to send a message between
setting the flag and printing the bestmove. Not a problem in practice because
clients don't do that, but then what's the point of having that ignore flag to
begin with?
So my solution is that my input thread always reads out the arriving messages
and processes "stop", "quit", or "isready" immediately, but puts other messages
in a queue that will be processed once the search ends. If I don't discard
messages, I'll never erroneously lose some.
hgm
What should happen when the client sends "isready\n" twice in a single write,
or twice before the engine has a chance to read the pipe? is one "readyok"
sufficient, or does the engine need to send two replies? (Perhaps the
underlying question is, are some messages meant to be handled sequentially,
in a blocking manner, and some messages are meant to be handled as soon as
they are read, skipping ahead of "sequential" messages?)
I don't think there is any ambiguity here. Each "isready" will have to be
answered with a "readyok" when its turn to be processed from the input stream
comes up. I think it is pretty obvious that all messages should be handled
sequentially, unless explicitly specified otherwise. Any protocol would
degenerate to a total mess when commands would be allowed to be processed
in an arbitrary order.
The exception that is explicitly made states that "isready" must be replied to
even when searching (which would be perceived out-of-turn by those who interpret
the search and printing of "bestmove" as processing of the "go" command).
HMK states that "isready" is used both "to wait for the engine to be ready
again" and "to ping the engine to find out if it is still alive". When is
it the former and when is it the latter? If the client sends "stop" and then
"isready" in immediate succession, and the engine is still calculating, once
the engine reads the message should it wait to answer with "readyok" until
after it has stopped (because "isready" was sent after "stop") or immediately
(because "when the engine is calculating [...] the engine should immediately
answer with "readyok" without stopping the search")?
I also see no ambiguity there. It is for what the client wants to use it for.
The protocol specs are not a tutorial for how to develop clients. The specs just
say how the comment should be responded to by an engine: "readyok" indicates the
engine is ready to process any allowed incoming command immediately. The client
can use this for any purpose it sees fit. During search the only allowed
commands are "stop" or "ponderhit", and the engine should always be ready to
start processing those without delay. After receiving "stop" the engine is by
definition no longer searching/calculating. If it needs time to kill its search
threads, during which it is not ready for processing (as opposed to receiving!)
the next command (presumably a position-moves), it should defer the "readyok"
(replied to an "isready" it received after the "stop" command) until it is.
Usually clients would not be interested to know how much time the engine uses
here, though, as the time for winding down the ponder search would simply be
counted against the clock of the engine, which would start to run when the
client send the "go" command for the next move. (And not when it pleases the
engine to start that search).
Can the engine assume that the client will never send "setoption" while it is
calculating? That seems like a reasonable assumption, but HMK doesn't mention
it. If it does (suppose the client sends "stop" and "setoption" in immediate
succession, so that the engine recieves "setoption" while it is still
calculating, before it has had time to stop, or perhaps the client simply
sends "setoption" in the middle of a search), should the engine ignore the
"setoption" or apply the change once it has stopped?
As far as the client is concerned an engine is only calculating between the
"go" and the "stop" command. So yes, clients can send "stop" and "setoption" in
immediate succession. (The need to set the option could actually have been the
reason for sending the "stop" during pondering, so this is in fact a likely
scenario.)
HMK does not require the client to send "isready" before a search (only once
at the beginning to "wait for the engine to finish initializing"). HMK also
states that "if the engine receives a command which is not supposed to come,
for example "stop" when the engine is not calculating, it should also just
ignore it." By the same token, one might expect that the engine should ignore
"go" if it receives "go" during a search. If the engine recieves a go message
while it is still calculating (because the client sent "stop" and "go" in
immediate succession and the engine read the go message before it had halted),
should it ignore the "go" message?
Of course not. It is not a command that "is not supposed to come". You confuse
"calculating" with "wasting time for internal reasons". As soon as an engine
receives "stop", it is no longer "calculating" in the sense of the protocol, and
whatever it is doing is just "processing of the previous command (stop)". There
is no rule that you cannot send commands before processing of a previous command
finishes. If there was, clients would have to poll the engine with "isready" and
wait for the "readyok" before every command other than "go".
Or should it queue up an indefinite number of go messages?
Of course it should queue up an indefinite number of commands. Not doing that
would be tantamount to randomly ignoring commands from the input stream just
because they happened to arrive at a moment when the engine was still working
on processing a previous command. Clients in general would not know when that
occurs. Normally you would not even have to think about this, because the
pipe mechanism in the operating system does it. All commands are queued in the
pipe, and the engine reads them away one by one, and processes them as soon as
it reads them. It will be impossible for the engine to determine how long a
command has been waiting in the pipe.
Or just one? (Perhaps the underlying question is, for the purposes of UCI,
is the engine's current state defined by client messages (either defined by
client messages being sent or defined by client messages being read), or
defined by both client and engine messages, or is the engine's state defined
by unobservable, internal variables?)
As clients and engines cannot know each other's timing of events, I don't see
how it could be any other than that, as far as the client is concerned, the
state of the engine is determined by the commands the client sends, while for
the engine the state is defined by the commands the engine receives. It seems
to me that any other interpretation would need violation of the laws of nature,
and thus is probably not what was meant.
I don't see how the existing specs leave any of these issues undefined. But I
see how your description (in particular the state diagram) conflicts with the
original specs. So it is just not a description of UCI.
hgm
So my solution is that my input thread always reads out the arriving
messages and processes "stop", "quit", or "isready" immediately, but puts
other messages in a queue that will be processed once the search ends.
If I don't discard messages, I'll never erroneously lose some.
This sounds wrong, because it opens the possibility for "isready" to be
processed out of order. While its very purpose could have been to test whether
all preceding command (which you have in fact queued) have been processed.
I also don't see why you would have to do any explicit queueing. The simple
implementation would be to just read the commands one by one from stdin, and
only start reading the next command when the engine is done processing the
previous one. This would happen naturally for commands processed by the input
thread itself (like "setoption" and such). The only case I see where you would
have to wait for an internal event is after "stop", where you might want to
wait for the search threads to acknowledge that they indeed have all stopped.
(This would usually be after one of them had printed "bestmove", even though
that is not strictly necessary.) So then you simply do not read input until
this is acknowledged.
You might argue that this would make it impossible to react to a "quit" command
when the search fails to properly stop. But this indicates a crash, and it is
questionable whether the engine would be able to properly execute a "quit"
command in that case. If you worry about that you could have the input thread
apply a timeout to its waiting for the stop acknowledgement, and quit the engine
spontaneously (perhaps with an error message "fails to stop").
Ras
This sounds wrong, because it opens the possibility for "isready" to be
processed out of order. While its very purpose could have been to test whether
all preceding command (which you have in fact queued) have been processed.
That's why I have additional logic for commands that may not be finished
instantaneously: "ucinewgame" and changing the hash table size. That's because
"ucinewgame" clears the entire hash table while reallocating it not only does
calloc, but also forcefully writes data and then clear the tables again. That
forces the OS to actually blend in the pages instead of just having them mapped
to the zero page and doing the remap later in search at the expense of engine
performance.
I also don't see why you would have to do any explicit queueing.
Because "isready" has to be answered at all times, also during search, and the
clean way is a dedicated input thread. So what if the engine has received the
"go" command and is calculating? Process the commands and discard all that are
not "stop" or "isready"? See my posting above why I don't do it that way.
expo
I think it is pretty obvious that all messages should be handled sequentially,
unless explicitly specified otherwise.
One of my goals is to remove the need for subjective judgments, whether obvious
or not, because obviousness varies from person to person and sometimes intuitive
interpretations fall apart in unusual circumstances.
For example, you wrote that "[e]ach 'isready' will have to be answered with a
'readyok' when its turn to be processed from the input stream comes up." HMK
does not mention queues, streams, or pipes. Although the engine's stdin and
stdout will likely be pipes, it's not explicit that the engine should handle
some messages as soon as they are read and divert some messages to a queue, and
that messages cannot be coalesced even if the stated purpose of the message
would still be accomplished. Edit: this is incorrect; the proper model seems
to be that all messages are queued but some are contextually nonblocking
(e.g. in between a "go"-"stop" pair, "isready" is nonblocking, but after
"stop" or between "stop" and "go", it is blocking), where "nonblocking" means
"requiring immediate response" and "blocking" means "causing message processing
to be paused until a moment at the engine's discretion" (that is, contingent on
some internal state of the engine).
During search the only allowed commands are "stop" or "ponderhit".
This is not stated in HMK, in fact!
After receiving "stop" the engine is by definition no longer searching.
But "searching" isn't defined in HMK!
It's actually a term I avoided using, because there may be some engines that
don't perform a search but simply return the top recommendation of a policy
network.
Of course not. It is not a command that "is not supposed to come". You confuse
"calculating" with "wasting time for internal reasons". As soon as an engine
receives "stop", it is no longer "calculating" in the sense of the protocol,
and whatever it is doing is just "processing of the previous command (stop)".
This isn't a confusion on my part; it was an attempt to point out that the
specification never defines "calculating". The difference between calculating
and wasting time cannot be part of a protocol, because protocols are merely
rules of communication and cannot refer to the internal state of the engine
(because internal representations may differ from engine) or the host device
(is the engine calculating when its processor usage is above 10%? above 5%?
and so on). So you could infer that "calculating" must be defined in terms of
messages sent, but this is contradicted by the choice of term; "calculating"
strongly suggests some internal condition of the engine. The same goes for
the term "searching".
As clients and engines cannot know each other's timing of events, I don't see
how it could be any other than that, as far as the client is concerned, the
state of the engine is determined by the commands the client sends, while for
the engine the state is defined by the commands the engine receives.
This is why the model currently used in the draft does not attempt to define
the state of the engine or client; state is a global property and the transition
rules are designed to ensure consistency regardless of the time between when
messages are enqueued and when they are dequeued. (This is mentioned in the
comment beginning at the bottom of page 5.)
I don't see how the existing specs leave any of these issues undefined. But I
see how your description (in particular the state diagram) conflicts with the
original specs. So it is just not a description of UCI.
I'd contend that there is no such thing as UCI – or less dramatically, that the
term UCI is descriptive, rather than prescriptive, referring to a collection of
behaviors that several programs have rather than a precise protocol. I don't
think UCI can be defined as "whatever Shredder does", because I don't believe
this is what most people have in mind. What I suspect most people have in
mind is "my engine supports UCI if it mostly works with cutechess or the 'UCI'
clients that I use" or "my client supports UCI if it mostly works with Stockfish
and other 'UCI' engines that I use" and so on. Which is a workable definition,
but one that I think can be improved upon, because I've seen implementation
divergence and confusion (myself, for one).
But this is beside the point, and you're entirely right: this formalization
conflicts with the behavior of existing clients in a few ways (where legacy
engines will work with new clients but legacy clients may not work with new
engines). So maybe it should have a slightly different name.
hgm
Because "isready" has to be answered at all times, also during search, and the
clean way is a dedicated input thread. So what if the engine has received the
"go" command and is calculating? Process the commands and discard all that are
not "stop" or "isready"? See my posting above why I don't do it that way.
Isn't that what the UCI specs prescribe? "If the engine receives a command which
is not supposed to come, it should also just ignore it." During search commands
other than "isready", "stop", or "ponderhit" are not supposed to come.
So how is queueing them anything other than non-compliance?
And "isready" doesn't have to be answered at all times. It has to be answered
when all commands received before it have finished processing. That is the
entire point of sending "isready" after setting the options at startup; options
like "Hash" or tablebase loading might take a long time, and the client doesn't
want to start the engine's clock before the latter has finished that. Replying
with "readyok" before that time would defeat the purpose.
Ras
Isn't that what the UCI specs prescribe? "If the engine receives a command
which is not supposed to come, it should also just ignore it." During search
commands other than "isready", "stop", or "ponderhit" are not supposed to
come.
That's the ideal case. In practice, see the posting with the race conditions.
So how is queueing them anything other than non-compliance?
That's a non-compliance without functional impact as long as everything works as
it should. The risk for race conditions and erroneous discard would have
functional impact and be even less compliant.
And "isready" doesn't have to be answered at all times.
Yeah, with or without search, and in particular during search, that's what I
meant.
Replying with "readyok" before that time would defeat the purpose.
Correct. Please read the posting that you just replied to ;)
hgm
That's the ideal case. In practice, see the posting with the race conditions.
This has nothing to do with race conditions. Race conditions do not alter the
order in which commands arrive at a given destination. They only apply to the
timing of traffic in different directions.
Ras
This has nothing to do with race conditions. Race conditions do not alter the
order in which commands arrive at a given destination. They only apply to the
timing of traffic in different directions.
Please see my first posting in this thread where I explained what exactly I was
referring to.
hgm
One of my goals is to remove the need for subjective judgments, whether
obvious or not, because obviousness varies from person to person and
sometimes intuitive interpretations fall apart in unusual circumstances.
Well, very nice. But then just supplement the official specs with a few
sentences stating the obvious. But don't mislead people in a way that would
cause their engine to fail.
For example, you wrote that "[e]ach 'isready' will have to be answered with
a 'readyok' when its turn to be processed from the input stream comes up."
HMK does not mention queues, streams, or pipes. Although the engine's stdin
and stdout will likely be pipes, it's not explicit that the engine should
handle some messages as soon as they are read and divert some messages to
a queue, and that messages cannot be coalesced even if the stated purpose of
the message would still be accomplished. Edit: this is incorrect; the proper
model seems to be that all messages are queued but some are contextually
nonblocking (e.g. in between a "go"-"stop" pair, "isready" is nonblocking, but
after "stop" or between "stop" and "go", it is blocking), where "nonblocking"
means "requiring immediate response" and "blocking" means "causing message
processing to be paused until a moment at the engine's discretion" (that is,
contingent on some internal state of the engine).
Sorry, but this is all complete nonsense. Stdin and stdout are streams. They
can only be read in sequential order. Whether the input is a pipe or not is also
irrelevant; when it would be the keyboard the program also doesn't get to see
what you typed last before it has read away what you typed first. Engines cannot
execute commands that they have not read yet. The engine is not in any position
to decide what the "stated purpose of the message" would be; Clients can send
commands for purposes only known to themselves. The blocking/non-blocking claims
are simply false. Between "go" and "stop" an "isready" can also be blocking: the
"go" might take some time to set up the search (creating and initializing search
threads, clearing the hash table), and the "isready" should not be replied to
before it is done with that and ready to receive and execute a "stop". Why is
it so hard to believe that an engine should reply to "isready" only when it is
ready?
This is not stated in HMK, in fact!
Well, what other commands did you have in mind, then? There aren't that many
client-to-engine commands, and "setoption" is the only one that makes sense
in this context. And for that the specs say: "this will only be sent when the
engine is waiting." Is there any doubt that an engine that is calculating or
searching is not waiting?
In fact the specs do explicitly state that a "debug" command can come while
thinking. So I should add that to the list of allowed commands. Never any reason
to defer its execution, though, as it is obviously only intended to set some
flag that controls the amount of output the engine produces during thinking.
So it can and should always be executed immediately. If it is implemented at
all; otherwise it is just ignored.
It seems to me that "uci" is only "supposed to come" at startup, and "register"
only on the engine's request. That only leaves "ucinewgame", "position" and
"go", which would all be highly disruptive to a search in progress. (Or isn't
that obvious enough?)
But "searching" isn't defined in HMK!
It's actually a term I avoided using, because there may be some engines that
don't perform a search but simply return the top recommendation of a policy
network.
Well, I used it as synonymous for "calculating" that is used in the specs. If
you want to state the obvious, just add the sentence "calculating is what the
engine does between executing the 'go' and the 'stop' command, or its unprompted
production of a 'bestmove' output".
This isn't a confusion on my part; it was an attempt to point out that the
specification never defines "calculating". The difference between calculating
and wasting time cannot be part of a protocol, because protocols are merely
rules of communication and cannot refer to the internal state of the engine
(because internal representations may differ from engine) or the host device
(is the engine calculating when its processor usage is above 10%? above 5%?
and so on). So you could infer that "calculating" must be defined in terms of
messages sent, but this is contradicted by the choice of term; "calculating"
strongly suggests some internal condition of the engine. The same goes for
the term "searching".
It should be obvious that executing commands takes non-zero time, even when
it is only very little. And that sometimes it can be a lot (e.g. loading EGT).
In the specs not all such time usage is considered "calculating", however. The
latter term is reserved for what the engine does to find a move in response to
a "go" command.
This is why the model currently used in the draft does not attempt to
define the state of the engine or client; state is a global property and
the transition rules are designed to ensure consistency regardless of the
time between when messages are enqueued and when they are dequeued. (This
is mentioned in the comment beginning at the bottom of page 5.)
In real life the state is a product of client and engine state, and both
sending and receiving messages causes state change. If you are ignoring that
you are certainly not talking about a global property. It seems to me that you
are talking about the state of just one of the agents, which happen to have the
same state diagram. But that for the engine the transitions occur when incoming
messages are received, and outgoing ones sent, while for the client transitions
it is just the other way around.
hgm
Please see my first posting in this thread where I explained what exactly I
was referring to.
It seems to me this is just a problem caused by clearing the "ignore input flag"
and sending "bestmove" in the wrong order. Like you say, the "ignore input flag"
should never have an effect when dealing with a compliant client. So the risk
that you would receive a position-go that would have had to be ignored by
clearing the flag early is virtually non-existent.
Ras
It seems to me this is just a problem caused by clearing the "ignore input
flag" and sending "bestmove" in the wrong order.
The observed problem arises when doing that in the seemingly obvious order: send
"bestmove", then clear the flag. But doing it the other way around also has its
own race condition, and that's non-trivial:
• engine clears the ignore flag
• engine sends "bestmove"
• the engine search gets scheduled away before actually exiting or finishing
• the next "go" comand arrives and a new search is spawned
• the old search threads get rescheduled and do final cleanup that may conflict
with the other, freshly spawned search
My conclusion was that the easiest and most reliable way of avoiding any sort
of that problem is simply not having such an ignore flag to begin with. You
won't notice such issues with a slow GUI such as Arena, but testing with the
fast and unforgiving c-chess-cli while using all logical cores will run into
such problems easily. I've seen that with several engines.
The only loophole I have is when a hash table resize arrives during search which
will be queued up, but since that's a non-trivial command that may take time,
the input thread will wait for confirmation from the worker (main) thread. That
won't come as long as that thread is in search. Hence, the input thread uses a
5 second timeout and will just continue without confirmation so that the input
thread will answer subsequent isready instead of blocking. The hash table resize
will be executed after the current search ends.
Like you say, the "ignore input flag" should never have an effect when dealing
with a compliant client.
But if I am free to assume that the client will be compliant, then buffering the
messages will not cause any functional noncompliance, either, because the whole
issue revolves around tight timing of correct command sequences.
My engine actually pushes these cases accidentally because I have several
conditions for quick responses using nearly no thinking time so that, for the
other engine, the time between sending its bestmove and receiving the next
position command can be extremely short. I'm also bypassing the whole C standard
library for extra I/O performance, geared towards games at 1s/game with no
increments.
hgm
The observed problem arises when doing that in the seemingly obvious order:
send "bestmove", then clear the flag. But doing it the other way around also
has its own race condition, and that's non-trivial.
I would consider clearing the flag after sending "bestmove" the obvious way
not to do it, because it invites the race condition: after "bestmove" you
can get commands that should not be ignored, and there is no guarantee that
the flag will be cleared before you receive them. And for the example of the
opposite case: this would also be avoided by sending "bestmove" only after
all threads have cleaned up. I guess doing otherwise would be cheating anyway:
in a non-ponder game engines are not supposed to do anything other than waiting
for input on the opponent's turn, which starts as soon as they send bestmove.
But if I am free to assume that the client will be compliant, then buffering
the messages will not cause any functional noncompliance, either, because the
whole issue revolves around tight timing of correct command sequences.
Perhaps not, but it is totally needless code complexity. If you would only send
"bestmove" after the search has cleaned up, you would not have to do any such
thing, and be still resistant to non-compliant client behavior. (Not that the
latter would help you getting sensible results under a rogue client...)
Ras
Perhaps not, but it is totally needless code complexity.
Code complexity, yes, but not needless. Since race conditions are nasty to
debug, and their absence cannot be proven through testing, I prefer a design
that will never lose messages simply because it doesn't discard any to begin
with. Even if it's not strictly in line with the protocol definition, as long
as it doesn't introduce new problems at functional level.
FulvioLet's recap it simply. The engine enters "search mode" after receiving
"go" and return to "wait mode" just before sending "bestmove". In "search mode"
the direction of communication is reversed. The engine repeatedly sends "info
messages" and the client is only allowed to send "stop" to ask the engine to
return to "wait mode".
hgm
Let's recap it simply.
There is a problem with that definition, because it delimits the "search mode"
(which the description calls "calculating") by communication events in opposite
directions. This can lead to race conditions. A specification that would cover
existing practice would say:
A "go" command will bring the engine in the "calculating" state, during which
it is not allowed to send it any other commands than "stop", "ponderhit",
"debug", and "isready" (and perhaps "quit"). This limitation lasts until the
"stop" command or the "bestmove" command (whichever comes earlier).
And as for stating the obvious:
An engine should execute allowed commands in the order it receives those.
It should not start executing a command before execution of all previous
commands has been completed, but should never delay execution any further
than that. Commands that are not allowed when their turn for execution comes
up should be ignored. Each of the other commands should be executed as per
their specification. It is not allowed to ignore commands just because the
engine feels their purpose has already been accomplished.
A "go" command is considered executed as soon as the engine has started
up calculation of the new move. Processing of subsequent commands must
start immediately after that, and not wait for this calculation to finish.
A "stop" command is considered executed when the engine would be ready to
initiate calculation of a new move.
syzygy
This is not stated in HMK, in fact!
Maybe not, but this is how UCI is universally understood. The philosophy of UCI
is to make things easy for the chess engine programmer (and thus hard for the
client programmer). There is no need for the chess engine to queue received
commands.
But "searching" isn't defined in HMK!
The engine is searching between receiving "go" and "stop".
It is true that the UCI "specification" is severaly lacking, but there is a lot
of common understanding of how it is to be understood. It does not seem to be
a good idea to plug the holes in the description in a way that is incompatible
with the common understanding.
syzygy
An engine should execute allowed commands in the order it receives those.
It should not start executing a command before execution of all previous
commands has been completed, but should never delay execution any further
than that. Commands that are not allowed when their turn for execution comes
up should be ignored. Each of the other commands should be executed as per
their specification. It is not allowed to ignore commands just because the
engine feels their purpose has already been accomplished. A "go" command is
considered executed as soon as the engine has started up calculation of the
new move. Processing of subsequent commands must start immediately after that,
and not wait for this calculation to finish. A "stop" command is considered
executed when the engine would be ready to initiate calculation of a new move.
What does it mean to "execute" a command?
CFish processes changes to the size of the hash table and the "LargePages"
option by keeping track of their mosr recent settings, and then resizes or
reallocates the hashtable (if necessary) only when something like "isready"
is received. Processing each "setoption" separately might lead to unnecessary
memory fragmentation.
There is no need for a protocol specification to prescribe how the engine should
implement things.
The UCI specification often states that if the client sends things at the wrong
time, those things should be ignored. In my view this can be removed from the
protocol. The client should simply behave correctly. If it does not, the engine
is free to do what it wants.
Another section that in my view makes no sense:
If the engine or the GUI receives an unknown command or token it should just
ignore it and try to parse the rest of the string in this line.
Example: "joho debug on\n" should switch the debug mode on given that joho is
not defined; "debug joho on\n" will be undefined, however.
What is the point of allowing (and ignoring) bogus at the beginning of a line?
I can see that it might be useful (even if questionable) to allow the client to
send non-UCI commands, which the engine may or may not understand. If the engine
does not understand it, it should then just ignore the whole line, not try to
make sense of what follows the unrecognised command.
If the client wants the engine to turn debug mode on, it will send "debug on\n".
It will not send "joho debug on\n" or "debug joho on\n". If the client sends
"joho debug on\n" and the engine does not recognise the "joho" command, there
is no reason for the engine to assume that the GUI wants the engine to turn
debug mode on.
hgmI suppose that in the implementation you mention execution of the
"setoption" command is just storing the new value, while actually clearing
the hash counts as execution of the "isready".
There really is no mystery here; the prescribed behavior is just what you get
when you read the commands from stdin by a blocking read call, and perform the
action they require at that time (which can be ignoring it) before you start
reading the next one. The only caveat is that "calculating" should be done in
the background, and you should start reading stdin again as soon as you have
started (after "go") or completely stopped (after "stop") that background task.
Ras
The UCI specification often states that if the client sends things at the
wrong time, those things should be ignored.
I think a better rewording would be that the engine may ignore these commands,
not should. That would be a hint rather to client authors than to engine
authors.
What is the point of allowing (and ignoring) bogus at the beginning of a line?
I ignore that part of the spec outright because it makes no sense. The beginning
of a line is always a command, and if the engine doesn't know a command, it
should not try to execute whatever arguments follow as if they were commands.
That's the wrong catagory, conflating commands with data. I don't see any
beneficial use case for that, only the potential for future trouble in case
of new commands.
With the tokens however, there is one case where it can make sense: within the
"go" command. If there were ever a new "go" parameter introduced, either as a
text-only token (like "infinite") or as text+value token (like "depth"), then
engines that don't know this new parameter should just ignore it, but evaluate
the rest. That's why I did follow that part of the spec for the "go" command.
Another case is engines with incomplete implementation. In particular, "mate"
and "searchmoves" are mandatory, but missing in some engines.
The problem with that potential use case is that UCI doesn't specify what
the "go" baseline behaviour should be, i.e. "go" without any parameter. The
consequence is that "go" followed by a new or non-implemented token as only
parameter would run into inconsistent engine behaviour.
Most of the "go" parameters (except "infinite") restrict the search amount in
some way, so my baseline is no restrictions in time or depth. Pretty much like
"go infinite", except that this also adds the requirement not to end the search
until stop arrives. This is the same behaviour as Stockfish, and that could be
clarified right in the spec.
hgm
I think a better rewording would be that the engine may ignore these commands,
not should. That would be a hint rather to client authors than to engine
authors.
I think this whole business of ignoring commands is just there to solve the
race condition on a "stop" command versus a spontaneously terminating search.
It would have been much better if the stop command was defined as a no-op
in case the engine was not "calculating". There really is no other use case,
unless you are dealing with a very sick client.
Ras
It would have been much better if the stop command was defined as a no-op in
case the engine was not "calculating".
I think that is the intention why "stop" is explicitely called out:
If the engine receives a command which is not supposed to come (e.g. "stop"
when the engine is not calculating), the engine should just ignore it.
syzygy
I think a better rewording would be that the engine may ignore these
commands, not should. That would be a hint rather to client authors than to
engine authors.
I would say the specification should make clear that the client may not send
things at the wrong time. The engine is then free to crash or to be defensive
when the client violates this.
An exception should be made for cases where the major clients violate this
requirement (and for cases where messages can cross each other, e.g. "stop" and
"bestmove").
I ignore that part of the spec outright because it makes no sense. The
beginning of a line is always a command, and if the engine doesn't know a
command, it should not try to execute whatever arguments follow as if they
were commands. That's the wrong catagory, conflating commands with data. I
don't see any beneficial use case for that, only the potential for future
trouble in case of new commands.
Exactly. I doubt that there are many engines, if any at all, that implement this
part of the specification.
With the tokens however, there is one case where it can make sense: within the
"go" command. If there were ever a new "go" parameter introduced, either as a
text-only token (like "infinite") or as text+value token (like "depth"), then
engines that don't know this new parameter should just ignore it, but evaluate
the rest. That's why I did follow that part of the spec for the "go" command.
Another case is engines with incomplete implementation. In particular, "mate"
and "searchmoves" are mandatory, but missing in some engines.
Interestingly the "debug joho on\n" example contradicts the earlier sentence
that unknown tokens should be ignored but the rest parsed.
The problem with that potential use case is that UCI doesn't specify what
the "go" baseline behaviour should be, i.e. "go" without any parameter. The
consequence is that "go" followed by a new or non-implemented token as only
parameter would run into inconsistent engine behaviour.
Most of the "go" parameters (except "infinite") restrict the search amount
in some way, so my baseline is no restrictions in time or depth. Pretty much
like "go infinite", except that this also adds the requirement not to end the
search until stop arrives. This is the same behaviour as Stockfish, and that
could be clarified right in the spec.
It seems to me that the requirement that "go infinite" must wait for "stop"
before ending the search only complicates the implementation of the engine with
zero benefit for the engine author. Stockfish had some race conditions here
which were finally solved by adding an ugly busy wait. Of course it is now too
late to change this part of the protocol, since clients probably depend on this
behaviour in analysis mode.
I agree that ignoring unknown/unexpected tokens in the "go" command (and in the
other direction in the "info" command) makes sense, and most engines probably
do this. A non-trivial question would be whether a "fixed" UCI spec should allow
the client to add non-standard parameters to the "go" command. I would tend to
say "no".
Fulvio
It seems to me that the requirement that "go infinite" must wait for "stop"
before ending the search only complicates the implementation of the engine
with zero benefit for the engine author.
I don't understand why this is so complicated. If the engine finishes early, it
just has to do a blocking readline until it receives "stop".
Ras
Stockfish had some race conditions here which were finally solved by adding
an ugly busy wait.
I use an event driven blocking wait in search with go infinite that also wakes
up once per second to print the status. That way, I avoid busy waiting and
burning cycles for nothing.
Of course it is now too late to change this part of the protocol, since
clients probably depend on this behaviour in analysis mode.
That's entirely possible.
A non-trivial question would be whether a "fixed" UCI spec should allow the
client to add non-standard parameters to the "go" command. I would tend to
say "no".
Same for new stuff in the info part. Existing tournament software relies on
the score for adjudication, and that would break with "score wdl value" or even
worse, several values. That would require a WDL-engine to also transmit a "cp"
score, at which point it can just stick with that anyway.
In my opinion, the stability of the ecosystem is not considered as much as it
should because that's a core feature of UCI – not making things nice on the
line where nobody really cares that much. The only thing that can be fixed is
clearing up some parts, but only in line with the existing common understanding.
Otherwise, the cost of breaking the ecosystem is too high for little to no added
value.
hgmOf course using a separate input thread is just asking for trouble.
For one it completely defeats the purpose of "isready" during search, namely
to probe whether the engine is still alive. The input thread automatically and
stupidly answers this always with "readyok", even if the search is completely
dead.
A much better design would have the main search thread periodically poll for
timeout or pending input. And then deal with the input if there is any (i.e.
reply to "isready", set its own abort flag on "stop", calculate its stopping
time after "ponderhit", adjust the debug flag on "debug", and ignore everything
else).
Ras
A much better design would have the main search thread periodically poll for
timeout or pending input.
Isn't that how that super ugly PeekNamedPipe hack came around? And also, the
polling idea doesn't mean that the engine isn't hanging. It only means that it's
not hanging in an area that doesn't include the polling.
syzygy
I don't understand why this is so complicated. If the engine finishes early,
it just has to do a blocking readline until it receives "stop".
But there will usually be an I/O thread doing the reading. So you need
interthread synchronisation to get it right. This is possible but my point is
that it needlessly complicates the life of the engine programmer, which is
against the UCI philosophy.
(Maybe there is a good reason for requiring that after "go infinite" the engine
sends the bestmove only after receiving "stop", but I don't see it. It seems to
me the client could simply save the received "bestmove" line until it needs it,
or discard it if it has no use for it. If this complicates the life of the
client programmer, then that is the point of UCI.)
However the problem with UCI is that it uses a varible number of tokens (for
example "infinite" is single, "depth" is composed of 2 tokens and "wdl" of 4
tokens). It is difficult to be sure that all the tokens of a new field would
be correctly ignored. In my opinion it is better to continue to use options
like "UCI_showWDL" to advertise new capabilities.
Indeed.
However, unknown tokens sent by the engine to the client in an "info" line seems
less problematic than unknown tokens in the "go" command. The "info" line does
not affect chess play, the "go" command does. It looks bad if an UCI-compliant
engine loses on time because the client uses a non-standard token to set the
time limit (e.g. when the player has selected a particular GUI menu option).
To parse "info" with unknown tokens, the client can discard tokens, including
numbers and other symbols, until it recognises a token such as "depth". This
only goes wrong if an unknown token is followed by the "depth" token or some
other known token as a parameter to that unknown token, and that should be
something that is relatively easy to avoid.
syzygy
Same for new stuff in the info part. Existing tournament software relies on
the score for adjudication, and that would break with "score wdl value" or
even worse, several values. That would require a WDL-engine to also transmit
a "cp" score, at which point it can just stick with that anyway.
This is a good refutation of what I just wrote in response to Fulvio. Indeed
the "info" lines can affect chess play if the client adjudicates based on the
reported scores.
In my opinion, the stability of the ecosystem is not considered as much as it
should because that's a core feature of UCI – not making things nice on the
line where nobody really cares that much. The only thing that can be fixed is
clearing up some parts, but only in line with the existing common
understanding. Otherwise, the cost of breaking the ecosystem is too
high for little to no added value.
We agree.
expo
Old clients with new engines will break the score display or adjudication.
It's already the case that engines need to choose to send "score cp" in order
for e.g. autoresignation to work with a particular client; nothing requires
engines to send this field. Specifying a format in which WDL information can be
given (if an engine wants to give WDL information) would be unrelated.
In general, the draft leaves the interpretation of search info to be
implementation-defined.* Given that this is merely a protocol, the semantics
of each message are left for clients and engines to decide. (However,
recommendations for interpretation are given as comments.)
*Almost necessarily, in most cases. For example, no two engines' centipawn units
are identically scaled, and for example, it would be difficult to define depth
or seldepth in a way that is comparable across all search algorithms and
implementations (indeed, some engines may not even have searches). And the
internal representations of engines are something I think a protocol
specification oughtn't concern itself with (even if the choices made in a
protocol's design are entirely made with the ease of engine implementation in
mind).
hgm
Isn't that how that super ugly peek-named-pipe-hack came around? And also,
the polling idea doesn't mean that the engine isn't hanging. It only means
that it's not hanging in an area that doesn't include the polling.
And how much good is that going to do you? You would still have to wait out the
entire game for a bestmove which would never come. The chances that an input
thread ever crashes are virtually non-existent, so by implementing "isready"
this way you have made its use during search completely pointless.
A proper implementation of "isready" should check whether the search is still
running, e.g. by having the search store the time it last checked the clock,
and comparing whether that is not too long before the moment the "isready" was
received.
And what is so hacky about PeekNamedPipe? It is just a system call for testing
in a simple way whether there is input waiting that you can read without
blocking. Of course you can use an event-driven design, which uses a "select" to
wait for both timer and input events, whichever comes first, instead of polling
to get immediate notification. But that is way more complex, and in practice you
can poll frequently enough without significantly slowing down the engine so that
the poll delay is insignificant.
hgm
And in light of that observation, a name like UCI version 2 seems perfectly
reasonable.
As long as you make it clear that it is version 2 of the protocol, and not
version 2 of the specification. Perhaps you could start the document with a
sentence like, "Those who do not mind that their engine might not work on every
UCI client can built it to the following specifications."
expo
As long as you make it clear that it is version 2 of the protocol, and not
version 2 of the specification. Perhaps you could start the document with a
sentence like, "Those who do not mind that their engine might not work on
every UCI client can built it to the following specifications."
Ah, that's a nice distinction! I'll add something to that effect.
Ras
A proper implementation of "isready" should check whether the search is still
running, e.g. by having the search store the time it last checked the clock,
and comparing whether that is not too long before the moment the "isready"
was received.
Yeah, or a watchdog-like thing where upon receiving "isready", the input thread
sets some variable and replies once search has reset it, which it could do every
so many nodes.
And what is so hacky about PeekNamedPipe?
It's Windows-only, and using an input thread that is allowed to block on stdin
is much simpler.
expo
Over and over again: it's the ecosystem. It's not hard, but as long as you
refuse to consider the ecosystem or don't even see what that means, failure of
this UCIv2 is probably the best outcome of this endeavour.
I believe I understand you; I think we're just talking past each other. I'm
not trying to challenge the primacy of an ecosystem: a healthy ecosystem is
absolutely necessary, because the actual experience using the software is the
only thing that ultimately matters. My point is that the UCI ecosystem is
already fragmented.
There is variation in clients' and engines' interpretations of the informal
specification, variation in strictness, and wide variation in feature support
(go nodes, multipv, ...). For example, some clients require the pv to be at the
end of info, some don't. Some clients require multipv to always be present when
the pv is present, some don't. Some clients send "go depth 245" when they want
an infinite search,* some send "go infinite".
Clients and engines currently have different (incompatible) assumptions and
requirements, so things are already broken. Codifying a particular behavior
doesn't change that either way. (And it can never prevent people from making
additions or modifications, or writing half-implementations.) However, it might
make it easier for people to converge.
Admittedly, if you restrict the "UCI ecosystem" to popular engines and clients
that are 5 or more years old, it is probably less fragmented than what I observe
working with brand new clients and new engines. I'm motivated by the observation
that new engine developers often have to make fixes or write hacks whenever they
want to use a new client.
The indictment could be made, "they simply misunderstood the specification" or
"they were lazy" or "they wrote an incorrect implementation", and those are all
likely true! I'm interested in why that happens and what can be done to reduce
the chance of it.
What seems most useful for new engine and client developers is a set of tests
that would tell you exactly what your engine (or client) does correctly and
what it gets wrong. That first requires deciding exactly what may or may not
be assumed by engines and clients. (So my ruckus about "protocol version 2"
is mostly just a prelude to what I hope will be the beneficial thing.)
I lean toward asking more from clients in order to simplify engine
implementation because there are fewer clients than engines. I'm planning to
reach out to client developers and ask them if they'd be willing to make their
clients more careful or lenient (if needed), and then I want to help them in
whatever way I can.
At least, that's the long-term vision. I expect a project like this to take
several years. And maybe, like you say, it would be better if it failed.
*This happens to be Stockfish's maximum depth, so I assume that's where the
number came from.
hgmWhatever the specifications are, there will always be developers that
violate those. The more precise and restrictive the specifications are, the
more often they will be violated. The longer and more elaborate they are, the
more people will not bother reading them, and consequently violate them.
What would help is publish a maximally simple example of a correct
implementation. Then all the lazy people that would not bother carefully
reading specs would clone that.
And it would probably be a good idea to develop a "malicious" client that
subjects an engine to all kinds of nasty boundary cases, and detects in what
aspects they fail.
None of this seems to require a change in the protocol.
expoI appreciate the discussion so far – it's been really useful and has
changed my mind about a few things. It's also been somewhat disappointing,
though, since by "feedback [on the draft]" I was asking for specific critiques.
For example, things like "in 5·6, it says that 'lowerbound' and 'upperbound'
have to come after 'cp int ', and all engines seem to do that, but clients
should actually be required to handle both orderings because...".
syzygy
This is more or less what I'm proposing (hence the "protocol" remark that the
draft introduces).
I do think that formalizing the Universal Chess Interface, i.e. removing as
much as possible the unclear and ambiguous parts of the specification while
being ruthless in keeping compatibility with the major GUIs and as many engines
as possible, could be a useful exercise. But clearly that is not what you are
planning to do.
By UCIv2 I don't mean an "improved" protocol specification but an extension of
the protocol to introduce features missing from UCI (so UCI itself wouldn't have
to be hacked and incompatibilities introduced). Instead of an UCIv2 option it
could also be "UCI_<feature>" as Fulvio suggests. Basically how Chess960 is done
in UCI: if the engine's "uci" command does not list "UCI_Chess960", the client
will not do anything with it, and if the client does not set "UCI_Chess960",
the engine will not do anything with it.
syzygyIf you do want to "complete" the spec of the UCI protocol and not
produce a new protocol (that should then definitely not be called UCI), then
I would say there is no fixed order except probably for "pv" being at the end.
Give maximum freedom to the engine programmer as long as the major clients don't
assume otherwise.
thomasahle
For example, things like "in 5·6, it says that 'lowerbound' and 'upperbound'
have to come after 'cp int ', and all engines seem to do that, but clients
should actually be required to handle both orderings because...".
I actually recently implemented this with "cp lowerbound int", I think, or maybe
just "lowerbound cp int", I'm not sure. But it didn't work in any clients. I
didn't understand why, since I thought I had followed the UCI protocol. So I
thought it was just clients not supporting upper and lower bounds. Only when
I saw how Stockfish did it, and copied that, did it suddenly work.
I'm definitely a big fan of a more formal uci spec!