0

I have a lua script, running on a RHEL7 host, which has been written to connect to a MS SQL Server database (using FreeTDS / unixODBC) and retrieve a value. The script generally works OK, however, if the target server is offline/unavailable, it will just hang (for approximately 6 minutes 20 seconds), before something eventually times out and an error is returned. Want I want is to get this timeout down to a couple of seconds...

I feel like this should be simple to do, but I can't seem to figure out how to specify/enforce a connection timeout to the SQL Server.

There is a timeout configuration in the /etc/freetds.conf (see below), but this seems to have no effect.

The script is normally called from nginx (openresty), which is the main reason I want to keep the timeout low, however the same hanging/timeout behaviour is observed running the script independently.

Can anyone help me solve this? I've included the script and the contents of relevant related config files below.

UPDATE: I noticed something worth mentioning during further testing - if I point my test script at an unused IP address OUTSIDE of the same subnet, my timeout when attempting to do the SQL server connection is ~6 minutes 21 seconds (as previously mentioned). If I point it at an unused IP address in the same subnet though, my timeout is consistently ~9 seconds. I'm no networking expert, but this suggests the 2 timeouts I'm actually seeing here are influenced by the network/transport layer - perhaps my local network switch is simply dropping the packets to the unknown subnet?

This doesn't solve my original problem, but thought it was worth mentioning as an update...

getvalue.lua:

local odbc = require "odbc"
local keyvalue = "some_value"
local retval = ""

CNN_DRV = {
        {Driver='{FreeTDS}'};
        {Server='10.10.60.100,1433'};
        {Database='databasename'};
        {Uid='sqlusername'};
        {Pwd='sqlpassword'};
};

dbassert = odbc.assert
env,err = odbc.environment()
local cnn, err = env:driverconnect(CNN_DRV)
stmt,err = cnn:prepare("{?= call dbo.GetRetValForKeyValue(?)}")
ret = stmt:vbind_param_ulong(1, ret, odbc.PARAM_OUTPUT)
val = stmt:vbind_param_char(2, keyvalue)
dbassert(stmt:execute())

stmt:foreach(function(f1)
        if tonumber(f1)
                then
                retval = string.format("%d", f1)
                else
                retval = ''
        end
end
)

print(retval)

/etc/odbcinst.ini:

[FreeTDS]
Description = FreeTDS TDS driver (for Sybase/MS SQL)
Driver = /usr/lib64/libtdsodbc.so.0
Setup = /usr/lib64/libtdsS.so.2

/etc/freetds.conf:

# Global settings are overridden by those in a database
# server specific section
[global]
        # TDS protocol version
;       tds version = 4.2

        # Whether to write a TDSDUMP file for diagnostic purposes
        # (setting this to /tmp is insecure on a multi-user system)
;       dump file = /tmp/freetds.log
;       debug flags = 0xffff

        # Command and connection timeouts
;       timeout = 10
;       connect timeout = 10

        # If you get out-of-memory errors, it may mean that your client
        # is trying to allocate a huge buffer for a TEXT field.
        # Try setting 'text size' to a more reasonable limit
        text size = 64512

2 Answers2

0

You can try set SQL_ATTR_LOGIN_TIMEOUT for connection object before do connect.

An SQLUINTEGER value corresponding to the number of seconds to wait for a login request to complete before returning to the application. The default is driver-dependent. If ValuePtr is 0, the timeout is disabled and a connection attempt will wait indefinitely.

local env, err = odbc.environment()
local cnn = env:connection()
cnn:setlogintimeout(10)
local ok, err = cnn:driverconnect(CNN_DRV)

Update

Also there exists SQL_ATTR_CONNECTION_TIMEOUT attribute (but for ODBC 3.0)

An SQLUINTEGER value corresponding to the number of seconds to wait for any request on the connection to complete before returning to the application. The driver should return SQLSTATE HYT00 (Timeout expired) anytime that it is possible to time out in a situation not associated with query execution or login.

If ValuePtr is equal to 0 (the default), there is no timeout.

I do not expose it but you can try this one. (Can also checkout value for SQL_ATTR_CONNECTION_TIMEOUT in your header files).

local SQL_ATTR_CONNECTION_TIMEOUT = 113
cnn:setuintattr(SQL_ATTR_CONNECTION_TIMEOUT, 10)
moteus
  • 2,187
  • 1
  • 13
  • 16
  • I've just given this a try but unfortunately this hasn't made a difference. In the course of further testing though, I have spotted that the timeout I am getting is different depending on the target IP address - I'll add details to the original post – verticallygifted May 11 '17 at 12:22
  • Update response with SQL_ATTR_CONNECTION_TIMEOUT attribute – moteus May 11 '17 at 13:27
  • Thanks for the follow up suggestion - I've just tried this as well without any luck, however my ODBC version (unixODBC) is only 2.3.1. The latest version available appear to be 2.3.4. Should I try upgrading? – verticallygifted May 11 '17 at 14:30
  • ODBC 3.0 Is not driver version but API version. you can checkout you version by `cnn:odbcver()` or `cnn:odbcvermm()` – moteus May 11 '17 at 14:40
  • Just checkout source. And seems freetds only use SQL_ATTR_CONNECTION_TIMEOUT. https://github.com/FreeTDS/freetds/blob/4700255d728e7eaa74ee2635d987aa7923459282/src/odbc/odbc.c#L377 https://github.com/FreeTDS/freetds/blob/99183ed9e8536dfcb4ecaa86936ef55d10d4ea75/src/tds/login.c#L460 https://github.com/FreeTDS/freetds/blob/99183ed9e8536dfcb4ecaa86936ef55d10d4ea75/src/tds/login.c#L489 – moteus May 11 '17 at 16:19
0

I had a similar problem (using FreeTDS 1.2.3 / unixODBC) on a raspberry PI 3. The connection worked without problems but I couldn't get the timeout to work (if I forced the SQL server offline). I traced the code and realised that timeout parameter in freetds.conf never reached the freeTDS library so the function which waits for a packet (in packet.c) just hanged there forever.

My solution was to modify the freeTDS library with the changes below so that when reading the odbc.ini the timeout value is fed to the freeTDS library. I'm not sure if this is the right way to fix this or if I'm missing something but I couldn't find anything on web.

odbc.h (Added Timeout in the odbc param list)

#define ODBC_PARAM_LIST \
    ODBC_PARAM(Servername) \
    ODBC_PARAM(Server) \
    ODBC_PARAM(DSN) \
    ODBC_PARAM(UID) \
    ODBC_PARAM(PWD) \
    ODBC_PARAM(Address) \
    ODBC_PARAM(Port) \
    ODBC_PARAM(TDS_Version) \
    ODBC_PARAM(Language) \
    ODBC_PARAM(Database) \
    ODBC_PARAM(TextSize) \
    ODBC_PARAM(PacketSize) \
    ODBC_PARAM(ClientCharset) \
    ODBC_PARAM(DumpFile) \
    ODBC_PARAM(DumpFileAppend) \
    ODBC_PARAM(DebugFlags) \
    ODBC_PARAM(Encryption) \
    ODBC_PARAM(Trusted_Connection) \
    ODBC_PARAM(APP) \
    ODBC_PARAM(WSID) \
    ODBC_PARAM(UseNTLMv2) \
    ODBC_PARAM(MARS_Connection) \
    ODBC_PARAM(REALM) \
    ODBC_PARAM(ServerSPN) \
    ODBC_PARAM(AttachDbFilename) \
    ODBC_PARAM(ApplicationIntent) \
    ODBC_PARAM(Timeout)

connectparams.c (added timeout property at function and read it when getting dsn info

int
ODBCINSTGetProperties(HODBCINSTPROPERTY hLastProperty)
{

....

hLastProperty = definePropertyString(hLastProperty, odbc_param_Timeout, "10", "The timeout for connection and queries.");

return 1;
}

/** 
 * Read connection information from given DSN
 * @param DSN           DSN name
 * @param login    where to store connection info
 * @return 1 if success 0 otherwhise
 */
int
odbc_get_dsn_info(TDS_ERRS *errs, const char *DSN, TDSLOGIN * login)
{
  ....

  if (myGetPrivateProfileString(DSN, odbc_param_Timeout, tmp) > 0)
        tds_parse_conf_section(TDS_STR_TIMEOUT, tmp, login);

    return 1;
}

odbc.ini

[TestServer]
Driver=FreeTDS
Database=MyDatabase
Port=1433
Server=<<ip address>>
timeout=10
Ivan
  • 21
  • 3
  • This change has now been committed to master branch in FreeTDS so using latest version should allow you to set a timeout in the connection string or the odbc.ini – Ivan Aug 12 '20 at 00:35