Chinaunix首页 | 论坛 | 博客
  • 博客访问: 295114
  • 博文数量: 59
  • 博客积分: 2021
  • 博客等级: 大尉
  • 技术积分: 491
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-29 09:49
文章分类

全部博文(59)

文章存档

2011年(6)

2010年(11)

2009年(42)

我的朋友

分类: 系统运维

2009-11-01 17:55:07

Summary: This page makes a fine (and admittedly minor) point about the behavior of the Unix (socket API) connect() system call when it is interrupted by a signal. It points out how obscure the Single Unix Specification is, and notes that many existing Unix implementations seem to have f*cked this up somehow.

The question is this: if a blocking connect() (on a blocking stream socket, that is) is interrupted by a signal, returning EINTR, in what state is the socket left, and is it permissible to restart the system call? What happens if a second connect() with the same arguments is attempted immediately after one failed with EINTR?

The (hereafter, “the Spec”) is part of the 's , version 3 (note: you may need to register to read this; see also ). Here is the relevant part of it:

If the initiating socket is connection-mode, then connect() shall attempt to establish a connection to the address specified by the address argument. If the connection cannot be established immediately and O_NONBLOCK is not set for the file descriptor for the socket, connect() shall block for up to an unspecified timeout interval until the connection is established. If the timeout interval expires before the connection is established, connect() shall fail and the connection attempt shall be aborted. If connect() is interrupted by a signal that is caught while blocked waiting to establish a connection, connect() shall fail and set connect() to [EINTR], but the connection request shall not be aborted, and the connection shall be established asynchronously.

If the connection cannot be established immediately and O_NONBLOCK is set for the file descriptor for the socket, connect() shall fail and set errno to [EINPROGRESS], but the connection request shall not be aborted, and the connection shall be established asynchronously. Subsequent calls to connect() for the same socket, before the connection is established, shall fail and set errno to [EALREADY].

When the connection has been established asynchronously, select() and poll() shall indicate that the file descriptor for the socket is ready for writing.

Later on, when listing possible error codes for connect(), the Spec mentions:

The connect() function shall fail if:

[…]
[EALREADY]
A connection request is already in progress for the specified socket.

How does this answer the question above? That is, what is supposed to happen, according to the Spec, when a second connect() is attempted (with the same arguments) just after one which returned EINTR (on a blocking stream socket)?

To me it seems that the Spec is contradictory. On the one hand, it is stated that If the connection cannot be established immediately and O_NONBLOCK is not set for the file descriptor for the socket, connect() shall block for up to an unspecified timeout interval until the connection is established.—now that sort of implies, since no provision is made for exceptions, that the second call to connect() should continue the connection attempted by the first (and which has not been aborted but run asynchronously), and block until the connection is established (or an error is returned). Let us call this the “Liberal Behavior” in what follows (I will later explain that this is how Linux behaves). On the other hand, the EALREADY error is documented as being returned whenever a connection request is underway. So the Spec seems to say that the second call to connect() should fail with the EALREADY error code. Let us call this the “Unforgiving Behavior” in what follows (I will later explain that this is how Solaris behaves). One thing is certain: the Spec is highly unclear about this point.

I have asked several people's opinion as to how they read the Spec, and most seem to favor the “Unforgiving Behavior”: they say the Spec requires the second connect() call to fail with EALREADY. So admittedly it is how the Spec should be read; whether this is desirable behavior, on the other hand, is dubious (see below).

In Unix Network Programming, volume 1, section 5.9, W. Richard Stevens states:

What we are doing […] is restarting the interrupted system call ourself. This is fine for accept, along with the functions such as read, write, select and open. But there is one function that we cannot restart ourself: connect. If this function returns EINTR, we cannot call it again, as doing so will return an immediate error. When connect is interrupted by a caught signal and is not automatically restarted, we must call select to wait for the connection to complete, as we describe in section 15.3.

This, indeed, clearly describes the “Unforgiving Behavior” (connect() failing immediately when restarted); note that Stevens does not say which error code is produced, and indeed Solaris returns EALREADY but BSD returns EADDRINUSE (a highly illogical error code in my opinion).

The “Liberal Behavior” consists of making connect() like every other system call: it can be restarted with the same arguments, without thinking, so long as it returns EINTR (only one minor difference remains: EISCONN needs to be checked, to avoid a race condition between two calls to connect()). This seems to be what Linux does (whether this is against the Spec, is, as I say, a matter of interpretation, though most people seem to think that indeed it is). There is much to be said in favor of this “Liberal Behavior”: basically, the whole point of using blocking sockets is for system calls to block rather than stupidly returning a temporary error code (be it EWOULDBLOCK/EAGAIN, EINPROGRESS or EALREADY) and forcing us to use select() or poll() to know when that temporary error will be gone; of what use are blocking sockets at all, if we are forced to use select() or poll() anyway? This is my main reason for preferring the “Liberal Behavior”.

Besides, the “Liberal Behavior” makes things much easier to program. In clear, I can write the following:

/* Start with fd just returned by socket(), blocking, SOCK_STREAM... */
while ( connect (fd, &name, namelen) == -1 && errno != EISCONN )
  if ( errno != EINTR )
    {
      perror ("connect");
      exit (EXIT_FAILURE);
    }
/* At this point, fd is connected. */

—instead of having to write all this:

/* Start with fd just returned by socket(), blocking, SOCK_STREAM... */
if ( connect (fd, &name, namelen) == -1 )
  {
    struct pollfd unix_really_sucks;
    int some_more_junk;
    socklen_t yet_more_useless_junk;

    if ( errno != EINTR /* && errno != EINPROGRESS */ )
      {
        perror ("connect");
        exit (EXIT_FAILURE);
      }
    unix_really_sucks.fd = fd;
    unix_really_sucks.events = POLLOUT;
    while ( poll (&unix_really_sucks, 1, -1) == -1 )
      if ( errno != EINTR )
        {
          perror ("poll");
          exit (EXIT_FAILURE);
        }
    yet_more_useless_junk = sizeof(some_more_junk);
    if ( getsockopt (fd, SOL_SOCKET, SO_ERROR,
                     &some_more_junk,
                     &yet_more_useless_junk) == -1 )
      {
        perror ("getsockopt");
        exit (EXIT_FAILURE);
      }
    if ( some_more_junk != 0 )
      {
        fprintf (stderr, "connect: %s\n",
                 strerror (some_more_junk));
        exit (EXIT_FAILURE);
      }
  }
/* At this point, fd is connected. */

—which anyone will admit is longer (over five times longer, as a matter of fact) and more tedious to write. Hence my calling this behavior the “Liberal Behavior” because it does not force the programmer to go through all this pain just to connect a socket.

Unfortunately, my opinion has not been consulted in defining Unix implementations, nor in writing the Single Unix specification, so it seems that the “Liberal Behavior” is not highly thought of, except under Linux, and anyone who wants to write a Unix program (except if it is to run solely on the Linux kernel) that performs the trivial act of opening a socket has to go through all the mess I have just written (and which, in case it is of any use, I put in the Public Domain). Why not use SA_RESTART on all signal handlers, some will ask? Well, the problem is that one is never quite sure that all signals have been treated, and just one signal is enough to interrupt system calls. Most people agree that SA_RESTART does not dispense you from testing EINTR everywhere, if you want to be safe. Anyway, it is not the purpose of this page to descuss this point.

Annoyingly, not only Unix implementations vary in this, but also the documentation is either imprecise or positively wrong.

Linux adopts the pleasant “Liberal Behavior” I have described. The man page for connect(2) under Linux does not document EINTR, however, nor does the info page of the GNU libc, whereas the code can be shown to occur: connect() can be interrupted by a signal under Linux (fortunately!).

Solaris adopts the “Unforgiving Behavior” that seems to be the literal interpretation of the Spec. However, the Solaris man page for connect(3SOCKET) states that the EALREADY error code occurs when The socket is non-blocking and a previous connection attempt has not yet been completed. But I have cases when the EALREADY error code was returned for a blocking socket (this is the very point I'm arguing about).

Both FreeBSD and OpenBSD adopt the “Unforgiving Behavior” with the following departure from the Spec that the error code returned on the second connect() call is EADDRINUSE rather than EALREADY. This is probably tradition, but it seems rather absurd. There is, however, one difference between FreeBSD and OpenBSD, but it concerns non-blocking sockets: OpenBSD returns EALREADY for non-blocking sockets as the Spec prescribes, whereas FreeBSD never seems to return EALREADY at all. However, this page is essentially about blocking sockets. Also, FreeBSD documents EALREADY (strange, since it does not return it), but not EINTR (which it does return); the OpenBSD man page is essentially correct. Note that in both cases the return code of EADDRINUSE is not clearly documented.

Information about the behavior of other Unixen, or other implementations of the Unix socket API, in this regard, is welcome.

See also on the comp.unix.programmer newsgroup (thanks to for making this available), where the issue was discussed (note that I mentioned EISCONN in error whereas I meant EADDRINUSE).

(Public Domain) will run tests on a given Unix implementation, to determine what the actual behavior happens to be, and display the result in a perfectly obscure and incomprehensible form. See the comments at the beginning of the file for information on use.

阅读(2376) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2009-12-18 23:20:10

这位兄弟,最近部门在内部推荐,机会较好,真诚希望你或者这里的朋友能关注下 ------------------------------------------------------------------------------------------ 全球TOP3通信公司创新项目招募人才(机会较好,待遇从优) 地点:上海   包括但不限于以下方面:(1)嵌入式技术和linux开发(2)编译环境开发(3)应用和业务软件开发(4)浏览器和互联网技术(5)媒体平台:媒体技术和算法(6)通信协议:传输,组网以及协议开发等   招聘范围:中高级技术专家,技术带头人,3年以上工作经验   待遇:从优,只要您有实力,待遇一切可谈   关于我们:提供一个良好的技术和开发环境,提供优越的个人发展空间通道以及完善的培训制度,个人配股激励计划。   若有任何疑问或者兴趣,欢迎邮件:BlankHt@163.com   静候您或者您的朋友回音。