Skip to content

Commit

Permalink
Reworked Request Timeout
Browse files Browse the repository at this point in the history
During timeout, will now call callback and fail the request.
Adding new parameter in FileNet_RequestFile to define the timeout period.
  • Loading branch information
Batfoxkid committed Dec 29, 2024
1 parent 814ab28 commit 2fd422d
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 83 deletions.
188 changes: 106 additions & 82 deletions scripting/filenetwork.sp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum struct FileEnum
any Data;

int Id;
int Timeout;
}

Handle SDKGetPlayerNetInfo;
Expand All @@ -39,7 +40,7 @@ char CurrentlySending[MAXPLAYERS+1][PLATFORM_MAX_PATH];
ArrayList RequestListing;
Handle RequestingTimer[MAXPLAYERS+1];
int CurrentlyRequesting[MAXPLAYERS+1] = {-1, ...};
int StartedRequestingAt[MAXPLAYERS+1];
int FailRequestingAt[MAXPLAYERS+1];

methodmap CNetChan
{
Expand Down Expand Up @@ -211,8 +212,6 @@ public void OnPluginStart()
failed = true;
}

//THIS ONLY WORKS ON WINDOWS!!!!!!
//linux uses a different solution, timeout logic.
detour = DynamicDetour.FromConf(gamedata, "CGameClient::FileDenied");
if(detour)
{
Expand Down Expand Up @@ -349,121 +348,145 @@ public MRESReturn OnFileReceived(Address address, DHookParam param)

RequestListing.Erase(i);
CallRequestFileFinish(info, true);

if(RequestingTimer[info.Client])
TriggerTimer(RequestingTimer[info.Client]);
delete RequestingTimer[info.Client];
SendNextRequest(info.Client);

break;
}
}
return MRES_Ignored;
}

void StartRequestingClient(int client)
{
if(!RequestingTimer[client])
{
RequestingTimer[client] = CreateTimer(1.0, Timer_RequestingClient, client, TIMER_REPEAT);
TriggerTimer(RequestingTimer[client]);
}
}

public Action Timer_RequestingClient(Handle timer, int client)
public MRESReturn OnFileDenied(Address address, DHookParam param)
{
CNetChan chan = CNetChan(client);
if(!chan)
return Plugin_Continue;

static FileEnum info;

if(CurrentlyRequesting[client] != -1)
{
// Client still giving this file
if((StartedRequestingAt[client] + TIMEOUT_PERIOD) > GetTime())
return Plugin_Continue;

// We timed out, this can be a case of the client getting stuck.
// Request a different file to unstuck the client then try again.
DeleteFile("download/scripts/cheatcodes.txt");

info.Client = client;
strcopy(info.Filename, sizeof(info.Filename), "scripts/cheatcodes.txt");
info.Func = INVALID_FUNCTION;
info.Id = chan.RequestFile(info.Filename);
RequestListing.PushArray(info);

CurrentlyRequesting[client] = info.Id;
StartedRequestingAt[client] += 10;
return Plugin_Continue;
}
int id = param.Get(2);
CNetChan chan = SDKCall(SDKGetNetChannel, address);

int length = RequestListing.Length;
for(int i; i < length; i++)
{
static FileEnum info;
RequestListing.GetArray(i, info);
if(info.Client == client)
if(info.Id == id && chan == CNetChan(info.Client))
{
info.Id = chan.RequestFile(info.Filename);
RequestListing.SetArray(i, info);

CurrentlyRequesting[client] = info.Id;
StartedRequestingAt[client] = GetTime();
float CurrentClientPing = GetClientAvgLatency(client, NetFlow_Outgoing);
CurrentClientPing *= 2.0; //beacuse it has to go bothways.
CurrentClientPing += 0.2; //Little buffer for ping issues and differences
Handle pack;
CreateDataTimer(CurrentClientPing, Timer_DeniedFileCheck, pack);
WritePackCell(pack, client); //Client
WritePackCell(pack, info.Id); //The id that we ask for
return Plugin_Continue;
}
}
if(CurrentlyRequesting[info.Client] == id)
CurrentlyRequesting[info.Client] = -1;

RequestListing.Erase(i);
CallRequestFileFinish(info, false);

// No more files to send
RequestingTimer[client] = null;
return Plugin_Stop;
delete RequestingTimer[info.Client];
SendNextRequest(info.Client);

break;
}
}
return MRES_Ignored;
}

public Action Timer_DeniedFileCheck(Handle timer, DataPack pack)
void StartRequestingClient(int client)
{
pack.Reset();
int client = pack.ReadCell();
int id = pack.ReadCell();
//We can presume the client didnt recieve the file if recieving didnt work out.
OnFileDeniedInternal(CNetChan(client), id);
return Plugin_Stop;
// Small delay between requesting files, Linux Server issue
if(!RequestingTimer[client])
RequestingTimer[client] = CreateTimer(1.5, Timer_RequestingClient, client, TIMER_REPEAT);
}

public MRESReturn OnFileDenied(Address address, DHookParam param)
void SendNextRequest(int client)
{
int id = param.Get(2);
CNetChan chan = SDKCall(SDKGetNetChannel, address);
OnFileDeniedInternal(chan, id);
return MRES_Ignored;
if(CurrentlyRequesting[client] == -1)
{
CNetChan chan = CNetChan(client);
if(chan)
{
int length = RequestListing.Length;
for(int i; i < length; i++)
{
static FileEnum info;
RequestListing.GetArray(i, info);
if(info.Client == client)
{
info.Id = chan.RequestFile(info.Filename);
RequestListing.SetArray(i, info);

CurrentlyRequesting[client] = info.Id;
FailRequestingAt[client] = GetTime() + info.Timeout;
StartRequestingClient(client);
break;
}
}
}
}
else
{
StartRequestingClient(client);
}
}

void OnFileDeniedInternal(CNetChan chan, int id)
public Action Timer_RequestingClient(Handle timer, int client)
{
if(CurrentlyRequesting[client] == -1)
{
SendNextRequest(client);
return Plugin_Continue;
}

// Client still giving this file
if(FailRequestingAt[client] > GetTime())
return Plugin_Continue;

/*
We timed out!
1. This can be a case of the client getting stuck from too many missing file requests.
2. A Linux server may not get the notice that the file was missing on the client.
Request a different file that exists to fix issue 1,
fail and return the callback to fix issue 2.
*/

CNetChan chan = CNetChan(client);
if(!chan)
return Plugin_Continue;

static FileEnum info;
int length = RequestListing.Length;
for(int i; i < length; i++)
{
static FileEnum info;
RequestListing.GetArray(i, info);
if(info.Id == id && chan == CNetChan(info.Client))
if(info.Id == CurrentlyRequesting[client] && info.Client == client)
{
if(CurrentlyRequesting[info.Client] == id)
CurrentlyRequesting[info.Client] = -1;
CurrentlyRequesting[info.Client] = -1;

RequestListing.Erase(i);
CallRequestFileFinish(info, false);

if(RequestingTimer[info.Client])
TriggerTimer(RequestingTimer[info.Client]);

break;
}
}

if(info.Plugin == null)
{
// We already tried unstucking with this method, don't try it again
SendNextRequest(client);
}
else
{
DeleteFile("download/scripts/cheatcodes.txt");

info.Plugin = null;
info.Client = client;
strcopy(info.Filename, sizeof(info.Filename), "scripts/cheatcodes.txt");
info.Func = INVALID_FUNCTION;
info.Id = chan.RequestFile(info.Filename);
info.Timeout = 30;
RequestListing.PushArray(info);

CurrentlyRequesting[client] = info.Id;
FailRequestingAt[client] = GetTime() + info.Timeout;
}
return Plugin_Continue;
}

static void CallRequestFileFinish(const FileEnum info, bool success)
{
if(info.Func && info.Func != INVALID_FUNCTION)
Expand Down Expand Up @@ -635,6 +658,7 @@ public any Native_RequestFile(Handle plugin, int params)
info.Plugin = plugin;
info.Func = GetNativeFunction(3);
info.Data = GetNativeCell(4);
info.Timeout = params < 5 ? TIMEOUT_PERIOD : GetNativeCell(5);

RequestListing.PushArray(info);

Expand Down
3 changes: 2 additions & 1 deletion scripting/include/filenetwork.inc
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,13 @@ native bool FileNet_SendFile(int client, const char[] file, FileNet_SendFileResu
* @param file Filepath of requested file
* @param callback Optional callback when file is done
* @param data Optional value to pass to the callback function
* @param timeout Timeout period in seconds
*
* @error Invalid client index, client is not in game, or a fake client
*
* @noreturn
*/
native void FileNet_RequestFile(int client, const char[] file, FileNet_RequestFileResult callback = INVALID_FUNCTION, any data = 0);
native void FileNet_RequestFile(int client, const char[] file, FileNet_RequestFileResult callback = INVALID_FUNCTION, any data = 0, int timeout = 90);

/**
* Gets the CNetChan object of a specific client
Expand Down

0 comments on commit 2fd422d

Please sign in to comment.