11
$\begingroup$

The LinkRead documentation states:

LinkRead will wait until it has read a complete expression before returning.

You can test whether an expression is ready to be read from a particular link using LinkReadyQ.

Therefore one must be careful with LinkRead. Trying to read from a link which has nothing on it will lock up the system (and in my experience aborting in such a situation can render the link unusable).

The LinkReadyQ documentation states,

LinkReadyQ[link] tests whether there is an expression ready to read from the specified WSTP connection.

If LinkReadyQ[link] returns True, then LinkRead[link] will not block under any normal circumstances.

Yet in the following example, LinkRead does block, even though LinkReadyQ said yes. What is happening here, and what am I doing wrong?


For this example, you will need two kernels running, (Kernel 1) and (Kernel 2).

From (Kernel 1),

link = LinkCreate["mylink"]
(* LinkObject["mylink", 1065, 13] *)

LinkReadyQ[link]
(* False *)

Now from (Kernel 2),

link = LinkConnect["mylink"]
(* LinkObject["mylink", 537, 4] *)

From (Kernel 1),

LinkReadyQ[link]
(* True *)

LinkRead[link]
(* This call blocks!! *)

This call did not return. I had no choice but to abort it. Now if I try any operations on the link in (Kernel 1), I get an error:

In[5]:= LinkReadyQ[link]

During evaluation of In[5]:= LinkObject::linkn: Argument LinkObject[mylink,1065,13] in LinkReadyQ[LinkObject[mylink,1065,13]] has an invalid LinkObject number; the link may be closed.

Out[5]= LinkReadyQ[LinkObject["mylink", 1065, 13]]

In (Kernel 2) I also get an error:

In[2]:= LinkWrite[link, 1]

During evaluation of In[2]:= LinkConnect::linkc: Unable to connect to LinkObject[mylink,537,4].

Out[2]= $Failed

Furthermore, I am unable to use the name "mylink" again until I close (Kernel 1).


Another scenario where LinkReadyQ returns True when it arguably shouldn't is

link = LinkConnect["foo"] (* foo is nonexistent *)
LinkReadyQ[link]
(* True *)

Perhaps this is a clear misuse of LinkConnect though.


This is related to How to implement LinkLaunch-like functionality with timeout?

$\endgroup$
  • 1
    $\begingroup$ You should try this: LinkCreate, LinkActivate, LinkRead. And vice versa for the other side of the link. I think you will understand why LinkCreate followed by LinkRead behaves the way it does. $\endgroup$ – ihojnicki Sep 11 '17 at 13:02
  • $\begingroup$ @ihojnicki So the answer is that before the link can be used, it must be activated (any functions may return garbage or fail before that)? MathLink is a bit underdocumented, and what I miss the most is a tutorial that puts down the basic concepts. There are a number of operations that must be performed in a certain sequence. Link creation seems to be one. This is not explained in the documentation (or I did not find it). Todd Gayley's old tutorial doesn't mention LinkActivate or MLActivate. Perhaps at that time it was called something else. $\endgroup$ – Szabolcs Sep 11 '17 at 13:21
  • $\begingroup$ @ihojnicki For this question, is it safe to abort LinkActivate, perhaps with TimeConstrained? I tried it, and aborting LinkActivate does not seem to have any bad effects: 1. evaluate LinkActviate 2. abort 3. evaluate it again 4. finish the connection process --> Now things seem to be working. But is there anything that went wrong due to that abort, even though I haven't noticed any symptoms yet? $\endgroup$ – Szabolcs Sep 11 '17 at 13:26
  • $\begingroup$ The reason why I was trying to use LinkReadyQ is that it has a second argument for time constraints. $\endgroup$ – Szabolcs Sep 11 '17 at 13:27
  • 1
    $\begingroup$ Yes the link must be activated before you can read/write data to it. In WL, that should happen automagically. Calling it manually illustrates that this is a blocking operation. The pattern we use is: LinkCreate/LinkConnect, LinkReady == true, LinkActivate, LinkReady == true, LinkRead. $\endgroup$ – ihojnicki Sep 11 '17 at 13:35
12
$\begingroup$

Sorry, it turns out there's a fairly critical omission in the documentation. LinkReadyQ, and its C cousins WSReady and MLReady have a very important secondary function when you're activating the link.

First, a bit of background. A link can have basically three states.

  • It has been opened, and is waiting to be activated.
  • It is open and activated.
  • It was opened and possibly activated, but a fatal error has occurred, and it's now dead.

You can think of the act of opening a link as returning a LinkObject or WSLINK which represents a promise, or a potential link. This is why doing something LinkCreate[] returns immediately. That link object might never fulfill its promise, but you at least know that it opened successfully. In order to fulfill its promise, the link must be activated.

So here's a more illustrative example.

In[1]:= lnk = LinkConnect["abc"]; MemberQ[Links[], lnk]

Out[1]= True

In[2]:= LinkRead[lnk]

During evaluation of In[2]:= LinkConnect::linkc: Unable to connect to LinkObject[abc,94,7].

Out[2]= $Failed

In[3]:= MemberQ[Links[], lnk]

Out[3]= False

Question...why did the LinkConnect ever succeed in the first place? There was nothing to connect to (in my example, "abc" doesn't exist as a listening link). The answer is that it succeeded because it didn't activate the link...it only opened the link and returned an object representing the link promise. Once you activate it, it's forced to "put up or shut up" as it were. Since the other side didn't exist when the activation was attempted, the failed activation turns into an error and the link is closed.

To activate a link, you can do one of two things. First, you can try to read from it or write to it. These operations implicitly activate the link, but that certainly poses a problem if you want to do better error-handling of the link creation process. If you want more control over the process, then you want to use LinkActivate[], or its C cousins WSActivate()/MLActivate(). These functions are potentially blocking functions which will either succeed and ensure the link is activated, or fail. In the Wolfram Language, this failure results in the deletion of the LinkObject. In C, this results in a dead link which you will have to call WSClose/MLClose on to clean up.

Now, with that understanding, let's go back and look at our possible states again...

  • Opened, not activated
  • Opened, activated
  • Dead

The third state isn't relevant for this discussion, but the first two definitely are. The documentation for LinkReadyQ and cousins is complete and correct for the second state. What the documentation does not say is that their behaviors are very different for the first state.

When the link is opened and not activated, LinkReadyQ and cousins return true if LinkActivate and cousins are likely to succeed. Which is to say that if you created a listening link, then somebody has connected to it on the other side. Or if you created a connecting link, then somebody is listening to it on the other side.

Note I used the word "likely". There are still potential problems. The big one might be that the other side hasn't tried to activate the link, in which case your attempt to activate it may hang indefinitely. And for this problem there is no solution, except that your two applications should define a state machine where you can ensure this doesn't happen. If one kernel or app exists solely to provide services to the other, then that app should probably just call LinkActivate and wait. For example, the FE and kernel define a state machine where the kernel agrees to simply activate and wait for the FE, since the kernel isn't doing anything else useful, anyway. Otherwise, if you have two apps that need to act as peers, then you might try calling WSActivate on a thread.

I'll fix the documentation for this. But you can take this behavior to the bank...the FE and kernel have relied on this for decades, and I imagine that quite a number of other Wolfram-created WSTP/MathLink programs do as well.

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.