Nudge control connection deadline before reading the data closing status
This commit is contained in:
		
							parent
							
								
									5d41901190
								
							
						
					
					
						commit
						53b6bfa4cd
					
				
							
								
								
									
										38
									
								
								ftp.go
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								ftp.go
									
									
									
									
									
								
							| @ -32,7 +32,8 @@ const ( | |||||||
| // It is not safe to be called concurrently. | // It is not safe to be called concurrently. | ||||||
| type ServerConn struct { | type ServerConn struct { | ||||||
| 	options *dialOptions | 	options *dialOptions | ||||||
| 	conn    *textproto.Conn | 	conn    *textproto.Conn // connection wrapper for text protocol | ||||||
|  | 	netConn net.Conn        // underlying network connection | ||||||
| 	host    string | 	host    string | ||||||
| 
 | 
 | ||||||
| 	// Server capabilities discovered at runtime | 	// Server capabilities discovered at runtime | ||||||
| @ -60,6 +61,7 @@ type dialOptions struct { | |||||||
| 	location    *time.Location | 	location    *time.Location | ||||||
| 	debugOutput io.Writer | 	debugOutput io.Writer | ||||||
| 	dialFunc    func(network, address string) (net.Conn, error) | 	dialFunc    func(network, address string) (net.Conn, error) | ||||||
|  | 	shutTimeout time.Duration // time to wait for data connection closing status | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Entry describes a file and is returned by List(). | // Entry describes a file and is returned by List(). | ||||||
| @ -120,6 +122,7 @@ func Dial(addr string, options ...DialOption) (*ServerConn, error) { | |||||||
| 		options:  do, | 		options:  do, | ||||||
| 		features: make(map[string]string), | 		features: make(map[string]string), | ||||||
| 		conn:     textproto.NewConn(do.wrapConn(tconn)), | 		conn:     textproto.NewConn(do.wrapConn(tconn)), | ||||||
|  | 		netConn:  tconn, | ||||||
| 		host:     remoteAddr.IP.String(), | 		host:     remoteAddr.IP.String(), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -148,6 +151,15 @@ func DialWithTimeout(timeout time.Duration) DialOption { | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DialWithShutTimeout returns a DialOption that configures the ServerConn with | ||||||
|  | // maximum time to wait for the data closing status on control connection | ||||||
|  | // and nudging the control connection deadline before reading status. | ||||||
|  | func DialWithShutTimeout(shutTimeout time.Duration) DialOption { | ||||||
|  | 	return DialOption{func(do *dialOptions) { | ||||||
|  | 		do.shutTimeout = shutTimeout | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // DialWithDialer returns a DialOption that configures the ServerConn with specified net.Dialer | // DialWithDialer returns a DialOption that configures the ServerConn with specified net.Dialer | ||||||
| func DialWithDialer(dialer net.Dialer) DialOption { | func DialWithDialer(dialer net.Dialer) DialOption { | ||||||
| 	return DialOption{func(do *dialOptions) { | 	return DialOption{func(do *dialOptions) { | ||||||
| @ -685,6 +697,24 @@ func (c *ServerConn) Stor(path string, r io.Reader) error { | |||||||
| 	return c.StorFrom(path, r, 0) | 	return c.StorFrom(path, r, 0) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // checkDataShut reads the "closing data connection" status from the | ||||||
|  | // control connection. It is called after transferring a piece of data | ||||||
|  | // on the data connection during which the control connection was idle. | ||||||
|  | // This may result in the idle timeout triggering on the control connection | ||||||
|  | // right when we try to read the response. | ||||||
|  | // The ShutTimeout dial option will rescue here. It will nudge the control | ||||||
|  | // connection deadline right before checking the data closing status. | ||||||
|  | func (c *ServerConn) checkDataShut() error { | ||||||
|  | 	if c.options.shutTimeout != 0 { | ||||||
|  | 		shutDeadline := time.Now().Add(c.options.shutTimeout) | ||||||
|  | 		if err := c.netConn.SetDeadline(shutDeadline); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	_, _, err := c.conn.ReadResponse(StatusClosingDataConnection) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // StorFrom issues a STOR FTP command to store a file to the remote FTP server. | // StorFrom issues a STOR FTP command to store a file to the remote FTP server. | ||||||
| // Stor creates the specified file with the content of the io.Reader, writing | // Stor creates the specified file with the content of the io.Reader, writing | ||||||
| // on the server will start at the given file offset. | // on the server will start at the given file offset. | ||||||
| @ -726,7 +756,7 @@ func (c *ServerConn) StorFrom(path string, r io.Reader, offset uint64) error { | |||||||
| 
 | 
 | ||||||
| 	// Read the response and use this error in preference to | 	// Read the response and use this error in preference to | ||||||
| 	// previous errors | 	// previous errors | ||||||
| 	_, _, respErr := c.conn.ReadResponse(StatusClosingDataConnection) | 	respErr := c.checkDataShut() | ||||||
| 	if respErr != nil { | 	if respErr != nil { | ||||||
| 		err = respErr | 		err = respErr | ||||||
| 	} | 	} | ||||||
| @ -748,7 +778,7 @@ func (c *ServerConn) Append(path string, r io.Reader) error { | |||||||
| 	_, err = io.Copy(conn, r) | 	_, err = io.Copy(conn, r) | ||||||
| 	errClose := conn.Close() | 	errClose := conn.Close() | ||||||
| 
 | 
 | ||||||
| 	_, _, respErr := c.conn.ReadResponse(StatusClosingDataConnection) | 	respErr := c.checkDataShut() | ||||||
| 	if respErr != nil { | 	if respErr != nil { | ||||||
| 		err = respErr | 		err = respErr | ||||||
| 	} | 	} | ||||||
| @ -889,7 +919,7 @@ func (r *Response) Close() error { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	err := r.conn.Close() | 	err := r.conn.Close() | ||||||
| 	_, _, err2 := r.c.conn.ReadResponse(StatusClosingDataConnection) | 	err2 := r.c.checkDataShut() | ||||||
| 	if err2 != nil { | 	if err2 != nil { | ||||||
| 		err = err2 | 		err = err2 | ||||||
| 	} | 	} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user