I believe this is all the diffs. My code is divergent from the base, but this should give context and intent. Basically, I'm grabbing a named semaphore in the instance of client in the zone process when we zone and not releasing it until all pertinent data is saved. The recipient zone tries to grab the same semaphore, which ends up delaying the recipient in cases where saving had not finished yet. I've seen it delay, and solve the pet gear issue. The code in the zone error functions are to release the semaphore in the originating zone if the zoning process is blocked in some way (flags, etc). Note there is a diff semaphore per char id, so multiple zoning clients don't slow each other down.
My fear, is other hard crashes after the zoning client grabs the sem, but before it is released.
Also note, we close/delete the sem on both sides, all the time, to avoid issues with leftover semaphores. Its temping to leave the sem there for future zoning, but the dynamic state of things makes that very risky.
Tons of printfs left in the code so you can test. Remove as you see fit.
Code:
=== modified file 'zone/zoning.cpp'
--- zone/zoning.cpp 2013-07-09 15:28:09 +0000
+++ zone/zoning.cpp 2014-07-28 14:16:48 +0000
@@ -36,6 +36,13 @@
#endif
zoning = true;
+
+ // Lock a semaphore so the new client won't read
+ // until we're done writing out data
+ // This will get released on client exit
+ OpenZoningSem();
+ WaitZoningSem();
+
if (app->size != sizeof(ZoneChange_Struct)) {
LogFile->write(EQEMuLog::Debug, "Wrong size: OP_ZoneChange, size=%d, expected %d", app->size, sizeof(ZoneChange_Struct));
return;
@@ -282,6 +289,11 @@
}
void Client::SendZoneCancel(ZoneChange_Struct *zc) {
+
+ // Not zoning, so client destructor won't be doing this.
+ PostZoningSem();
+ CloseZoningSem();
+
//effectively zone them right back to where they were
//unless we find a better way to stop the zoning process.
SetPortExemption(true);
@@ -302,6 +314,10 @@
{
LogFile->write(EQEMuLog::Error, "Zone %i is not available because target wasn't found or character insufficent level", zc->zoneID);
+ // Not zoning, so client destructor won't be doing this.
+ PostZoningSem();
+ CloseZoningSem();
+
SetPortExemption(true);
EQApplicationPacket *outapp;
=== modified file 'zone/client.cpp'
--- zone/client.cpp 2014-01-11 17:57:12 +0000
+++ zone/client.cpp 2014-07-24 15:25:58 +0000
@@ -230,6 +230,7 @@
zonesummon_ignorerestrictions = 0;
zoning = false;
zone_mode = ZoneUnsolicited;
+zoning_sem = (sem_t *) NULL;
proximity_x = FLT_MAX; //arbitrary large number
proximity_y = FLT_MAX;
proximity_z = FLT_MAX;
@@ -435,8 +436,107 @@
entity_list.RemoveClient(this);
UninitializeBuffSlots();
+
+// Clear the semaphore since this client is dying - zone/crash/logout
+PostZoningSem();
+CloseZoningSem();
}
+// Added these functions to make sure saving of data finished before new
+// zone loaded it in.
+
+void Client::OpenZoningSem()
+ {
+ printf("Trying to CREATE a semaphore for char id %d.\n", CharacterID());
+
+ // Use a semaphore named after character id.
+ sprintf(zoning_sem_name,"%d-%s", CharacterID(),name);
+
+ zoning_sem=sem_open(zoning_sem_name, O_CREAT, 0644, 1);
+
+ if (zoning_sem == SEM_FAILED)
+ {
+ printf("Cannot open zoning semaphore for char id %d.\n", CharacterID());
+ }
+ fflush(stdout);
+ }
+
+int Client::WaitZoningSem()
+ {
+ int ret;
+
+ printf("Trying to WAIT a semaphore for char id %d.\n", CharacterID());
+
+ ret = sem_trywait(zoning_sem);
+
+ if (ret != 0)
+ {
+ printf("LOCKED - WAITING\n");
+ ret = sem_wait(zoning_sem);
+ }
+
+ if (ret != 0)
+ {
+ printf("Error waiting on zoning semaphore.\n");
+ }
+
+ fflush(stdout);
+ return ret;
+ }
+
+int Client::PostZoningSem()
+ {
+ int ret=0;
+
+ printf("Trying to POST a semaphore for char id %d.\n", CharacterID());
+
+ // Its possible for the sem to not exist, if the client never zoned.
+ if (zoning_sem != (sem_t *) NULL)
+ {
+ ret= sem_post(zoning_sem);
+
+ if (ret != 0)
+ {
+ printf("Error posting on zoning semaphore.\n");
+ }
+ }
+
+ fflush(stdout);
+ return ret;
+ }
+
+int Client::CloseZoningSem()
+ {
+ int ret;
+
+ // This gets called by destructor, when the sem should already be closed
+ // So lets not do extra work if so.
+ if (zoning_sem != (sem_t *) NULL)
+ {
+ printf("Trying to CLOSE a semaphore for char id %d.\n", CharacterID());
+
+ ret = sem_close(zoning_sem);
+
+ if (ret != 0)
+ {
+ printf("Error closing on zoning semaphore.\n");
+ }
+
+ ret = sem_unlink(zoning_sem_name);
+
+ if (ret != 0)
+ {
+ printf("Error unlinking on zoning semaphore.\n");
+ }
+ }
+
+ fflush(stdout);
+
+ zoning_sem = (sem_t *) NULL;
+
+ return ret;
+ }
+
void Client::SendLogoutPackets() {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_CancelTrade, sizeof(CancelTrade_Struct));
=== modified file 'zone/client.h'
--- zone/client.h 2013-05-02 18:37:13 +0000
+++ zone/client.h 2014-07-23 19:11:48 +0000
@@ -19,6 +19,7 @@
#define CLIENT_H
class Client;
+#include <semaphore.h>
#include "../common/timer.h"
#include "../common/ptimer.h"
#include "../common/emu_opcodes.h"
@@ -491,6 +492,10 @@
inline uint32 GetGold() const { return m_pp.gold; }
inline uint32 GetPlatinum() const { return m_pp.platinum; }
+ void OpenZoningSem();
+ int WaitZoningSem();
+ int PostZoningSem();
+ int CloseZoningSem();
/*Endurance and such*/
void CalcMaxEndurance(); //This calculates the maximum endurance we can have
@@ -1303,6 +1308,8 @@
uint8 zonesummon_ignorerestrictions;
ZoneMode zone_mode;
+ sem_t *zoning_sem;
+ char zoning_sem_name[75];
Timer position_timer;
uint8 position_timer_counter;
=== modified file 'zone/client_packet.cpp'
--- zone/client_packet.cpp 2014-01-18 17:25:31 +0000
+++ zone/client_packet.cpp 2014-07-24 22:16:53 +0000
@@ -8971,7 +8971,13 @@
}
+ // Obtain a lock that proves the old zone is done
+ // Updating this data.
+ OpenZoningSem();
+ WaitZoningSem();
+
if (spells_loaded)
+
{
for(uint32 z=0;z<MAX_PP_MEMSPELL;z++)
{
@@ -9154,6 +9160,7 @@
rest_timer.Start(m_pp.RestTimer * 1000);
database.LoadPetInfo(this);
+
//this was moved before the spawn packets are sent
//in hopes that it adds more consistency...
//Remake pet
@@ -9169,9 +9176,15 @@
}
m_petinfo.SpellID = 0;
}
+
// Moved here so it's after where we load the pet data.
if(!GetAA(aaPersistentMinion))
memset(&m_suspendedminion, 0, sizeof(PetInfo));
+
+ // I think this was the last thing we need to load.
+ // Release the Semaphore.
+ PostZoningSem();
+ CloseZoningSem();
////////////////////////////////////////////////////////////
// Server Zone Entry Packet