track with timer API changes
This commit is contained in:
@@ -63,45 +63,15 @@ private:
|
||||
void callBack ();
|
||||
};
|
||||
|
||||
//
|
||||
// class casDGEvWakeup
|
||||
//
|
||||
class casDGEvWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casDGEvWakeup ( casDGIntfOS &osIn );
|
||||
virtual ~casDGEvWakeup();
|
||||
void show ( unsigned level ) const;
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
casDGIntfOS &os;
|
||||
expireStatus expire( const epicsTime & currentTime );
|
||||
};
|
||||
|
||||
//
|
||||
// class casDGIOWakeup
|
||||
//
|
||||
class casDGIOWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casDGIOWakeup ( casDGIntfOS &osIn );
|
||||
virtual ~casDGIOWakeup ();
|
||||
void show ( unsigned level ) const;
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
casDGIntfOS &os;
|
||||
expireStatus expire( const epicsTime & currentTime );
|
||||
};
|
||||
|
||||
//
|
||||
// casDGIntfOS::casDGIntfOS()
|
||||
//
|
||||
casDGIntfOS::casDGIntfOS (caServerI &serverIn, const caNetAddr &addr,
|
||||
bool autoBeaconAddr, bool addConfigBeaconAddr) :
|
||||
bool autoBeaconAddr, bool addConfigBeaconAddr) :
|
||||
casDGIntfIO (serverIn, addr, autoBeaconAddr, addConfigBeaconAddr),
|
||||
pRdReg (NULL),
|
||||
pBCastRdReg (NULL),
|
||||
pWtReg (NULL),
|
||||
pEvWk (NULL),
|
||||
pIOWk (NULL),
|
||||
pRdReg ( 0 ),
|
||||
pBCastRdReg ( 0 ),
|
||||
pWtReg ( 0 ),
|
||||
sendBlocked (false)
|
||||
{
|
||||
this->xSetNonBlocking();
|
||||
@@ -114,25 +84,15 @@ casDGIntfOS::casDGIntfOS (caServerI &serverIn, const caNetAddr &addr,
|
||||
casDGIntfOS::~casDGIntfOS()
|
||||
{
|
||||
this->disarmSend();
|
||||
|
||||
this->disarmRecv();
|
||||
|
||||
if (this->pEvWk) {
|
||||
delete this->pEvWk;
|
||||
}
|
||||
|
||||
if (this->pIOWk) {
|
||||
delete this->pIOWk;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// casDGEvWakeup::casDGEvWakeup()
|
||||
//
|
||||
casDGEvWakeup::casDGEvWakeup ( casDGIntfOS &osIn ) :
|
||||
timer ( fileDescriptorManager.createTimer() ), os ( osIn )
|
||||
casDGEvWakeup::casDGEvWakeup () :
|
||||
timer ( fileDescriptorManager.createTimer() ), pOS ( 0 )
|
||||
{
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -140,10 +100,20 @@ casDGEvWakeup::casDGEvWakeup ( casDGIntfOS &osIn ) :
|
||||
//
|
||||
casDGEvWakeup::~casDGEvWakeup()
|
||||
{
|
||||
this->os.pEvWk = NULL;
|
||||
delete & this->timer;
|
||||
}
|
||||
|
||||
void casDGEvWakeup::start ( casDGIntfOS &os )
|
||||
{
|
||||
if ( this->pOS ) {
|
||||
assert ( this->pOS == &os );
|
||||
}
|
||||
else {
|
||||
this->pOS = &os;
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// casDGEvWakeup::show()
|
||||
//
|
||||
@@ -158,19 +128,19 @@ void casDGEvWakeup::show ( unsigned level ) const
|
||||
//
|
||||
// casDGEvWakeup::expire()
|
||||
//
|
||||
epicsTimerNotify::expireStatus casDGEvWakeup::expire( const epicsTime & currentTime )
|
||||
epicsTimerNotify::expireStatus casDGEvWakeup::expire ( const epicsTime & currentTime )
|
||||
{
|
||||
this->os.casEventSys::process();
|
||||
this->pOS->casEventSys::process();
|
||||
this->pOS = 0;
|
||||
return noRestart;
|
||||
}
|
||||
|
||||
//
|
||||
// casDGIOWakeup::casDGIOWakeup()
|
||||
//
|
||||
casDGIOWakeup::casDGIOWakeup ( casDGIntfOS &osIn ) :
|
||||
timer ( fileDescriptorManager.createTimer() ), os ( osIn )
|
||||
casDGIOWakeup::casDGIOWakeup () :
|
||||
timer ( fileDescriptorManager.createTimer() ), pOS ( 0 )
|
||||
{
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -178,7 +148,6 @@ casDGIOWakeup::casDGIOWakeup ( casDGIntfOS &osIn ) :
|
||||
//
|
||||
casDGIOWakeup::~casDGIOWakeup()
|
||||
{
|
||||
this->os.pIOWk = NULL;
|
||||
delete & this->timer;
|
||||
}
|
||||
|
||||
@@ -200,10 +169,25 @@ epicsTimerNotify::expireStatus casDGIOWakeup::expire( const epicsTime & currentT
|
||||
// the input buffer, and there eventually will
|
||||
// be something to read from TCP this works
|
||||
//
|
||||
this->os.processInput ();
|
||||
this->pOS->processInput ();
|
||||
this->pOS = 0;
|
||||
return noRestart;
|
||||
}
|
||||
|
||||
//
|
||||
// casDGIOWakeup::show()
|
||||
//
|
||||
void casDGIOWakeup::start ( casDGIntfOS &os )
|
||||
{
|
||||
if ( this->pOS ) {
|
||||
assert ( this->pOS == &os );
|
||||
}
|
||||
else {
|
||||
this->pOS = &os;
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// casDGIOWakeup::show()
|
||||
//
|
||||
@@ -284,14 +268,7 @@ void casDGIntfOS::disarmSend ()
|
||||
//
|
||||
void casDGIntfOS::ioBlockedSignal()
|
||||
{
|
||||
if (!this->pIOWk) {
|
||||
this->pIOWk = new casDGIOWakeup(*this);
|
||||
if (!this->pIOWk) {
|
||||
errMessage (S_cas_noMemory,
|
||||
"casDGIntfOS::ioBlockedSignal()");
|
||||
throw S_cas_noMemory;
|
||||
}
|
||||
}
|
||||
this->ioWk.start ( *this );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -299,14 +276,7 @@ void casDGIntfOS::ioBlockedSignal()
|
||||
//
|
||||
void casDGIntfOS::eventSignal()
|
||||
{
|
||||
if (!this->pEvWk) {
|
||||
this->pEvWk = new casDGEvWakeup (*this);
|
||||
if (!this->pEvWk) {
|
||||
errMessage (S_cas_noMemory,
|
||||
"casDGIntfOS::eventSignal ()");
|
||||
throw S_cas_noMemory;
|
||||
}
|
||||
}
|
||||
this->evWk.start ( *this );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -339,12 +309,8 @@ void casDGIntfOS::show(unsigned level) const
|
||||
if (this->pBCastRdReg) {
|
||||
this->pBCastRdReg->show (level);
|
||||
}
|
||||
if (this->pEvWk) {
|
||||
this->pEvWk->show (level);
|
||||
}
|
||||
if (this->pIOWk) {
|
||||
this->pIOWk->show (level);
|
||||
}
|
||||
this->evWk.show (level);
|
||||
this->ioWk.show (level);
|
||||
this->casDGIntfIO::show (level);
|
||||
}
|
||||
|
||||
@@ -436,7 +402,7 @@ void casDGWriteReg::show(unsigned level) const
|
||||
//
|
||||
void casDGIntfOS::sendBlockSignal()
|
||||
{
|
||||
this->sendBlocked=TRUE;
|
||||
this->sendBlocked = true;
|
||||
this->armSend();
|
||||
}
|
||||
|
||||
@@ -453,7 +419,7 @@ void casDGIntfOS::sendCB()
|
||||
flushCond = this->flush();
|
||||
if (flushCond==flushProgress) {
|
||||
if (this->sendBlocked) {
|
||||
this->sendBlocked = FALSE;
|
||||
this->sendBlocked = false;
|
||||
}
|
||||
//
|
||||
// this reenables receipt of incoming frames once
|
||||
|
||||
@@ -41,8 +41,36 @@ private:
|
||||
class casDGReadReg;
|
||||
class casDGBCastReadReg;
|
||||
class casDGWriteReg;
|
||||
class casDGEvWakeup;
|
||||
class casDGIOWakeup;
|
||||
|
||||
//
|
||||
// class casDGEvWakeup
|
||||
//
|
||||
class casDGEvWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casDGEvWakeup ();
|
||||
virtual ~casDGEvWakeup();
|
||||
void show ( unsigned level ) const;
|
||||
void start ( class casDGIntfOS &osIn );
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
class casDGIntfOS *pOS;
|
||||
expireStatus expire( const epicsTime & currentTime );
|
||||
};
|
||||
|
||||
//
|
||||
// class casDGIOWakeup
|
||||
//
|
||||
class casDGIOWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casDGIOWakeup ();
|
||||
virtual ~casDGIOWakeup ();
|
||||
void show ( unsigned level ) const;
|
||||
void start ( class casDGIntfOS &osIn );
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
class casDGIntfOS *pOS;
|
||||
expireStatus expire( const epicsTime & currentTime );
|
||||
};
|
||||
|
||||
//
|
||||
// casDGIntfOS
|
||||
@@ -51,10 +79,7 @@ class casDGIntfOS : public casDGIntfIO {
|
||||
friend class casDGReadReg;
|
||||
friend class casDGBCastReadReg;
|
||||
friend class casDGWriteReg;
|
||||
friend class casDGEvWakeup;
|
||||
friend class casDGIOWakeup;
|
||||
public:
|
||||
|
||||
casDGIntfOS (caServerI &serverIn, const caNetAddr &addr,
|
||||
bool autoBeaconAddr=TRUE, bool addConfigBeaconAddr=FALSE);
|
||||
|
||||
@@ -62,13 +87,15 @@ public:
|
||||
|
||||
virtual void show (unsigned level) const;
|
||||
|
||||
void processInput();
|
||||
|
||||
private:
|
||||
casDGIOWakeup ioWk;
|
||||
casDGEvWakeup evWk;
|
||||
casDGReadReg *pRdReg;
|
||||
casDGBCastReadReg *pBCastRdReg; // fix for solaris bug
|
||||
casDGWriteReg *pWtReg;
|
||||
casDGEvWakeup *pEvWk;
|
||||
casDGIOWakeup *pIOWk;
|
||||
unsigned char sendBlocked;
|
||||
bool sendBlocked;
|
||||
|
||||
void armRecv ();
|
||||
void armSend ();
|
||||
@@ -86,7 +113,6 @@ private:
|
||||
void eventSignal ();
|
||||
void eventFlush ();
|
||||
|
||||
void processInput();
|
||||
};
|
||||
|
||||
//
|
||||
@@ -95,7 +121,6 @@ private:
|
||||
class casIntfOS : public casIntfIO, public tsDLNode<casIntfOS>,
|
||||
public casDGIntfOS
|
||||
{
|
||||
friend class casDGEvWakeup;
|
||||
friend class casServerReg;
|
||||
public:
|
||||
casIntfOS (caServerI &casIn, const caNetAddr &addr,
|
||||
@@ -113,8 +138,36 @@ private:
|
||||
|
||||
class casStreamWriteReg;
|
||||
class casStreamReadReg;
|
||||
class casStreamEvWakeup;
|
||||
class casStreamIOWakeup;
|
||||
|
||||
//
|
||||
// class casStreamIOWakeup
|
||||
//
|
||||
class casStreamIOWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casStreamIOWakeup ();
|
||||
virtual ~casStreamIOWakeup ();
|
||||
void show ( unsigned level ) const;
|
||||
void start ( casStreamOS &osIn );
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
casStreamOS *pOS;
|
||||
expireStatus expire ( const epicsTime & currentTime );
|
||||
};
|
||||
|
||||
//
|
||||
// class casStreamEvWakeup
|
||||
//
|
||||
class casStreamEvWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casStreamEvWakeup ();
|
||||
virtual ~casStreamEvWakeup ();
|
||||
void show ( unsigned level ) const;
|
||||
void start ( casStreamOS &osIn );
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
casStreamOS *pOS;
|
||||
expireStatus expire ( const epicsTime & currentTime );
|
||||
};
|
||||
|
||||
//
|
||||
// casStreamOS
|
||||
@@ -122,19 +175,20 @@ class casStreamIOWakeup;
|
||||
class casStreamOS : public casStreamIO {
|
||||
friend class casStreamWriteReg;
|
||||
friend class casStreamReadReg;
|
||||
friend class casStreamEvWakeup;
|
||||
friend class casStreamIOWakeup;
|
||||
public:
|
||||
casStreamOS (caServerI &, const ioArgsToNewStreamIO &ioArgs);
|
||||
~casStreamOS ();
|
||||
|
||||
void show (unsigned level) const;
|
||||
|
||||
casProcCond processInput ();
|
||||
|
||||
private:
|
||||
casStreamEvWakeup evWk;
|
||||
casStreamIOWakeup ioWk;
|
||||
casStreamWriteReg *pWtReg;
|
||||
casStreamReadReg *pRdReg;
|
||||
casStreamEvWakeup *pEvWk;
|
||||
casStreamIOWakeup *pIOWk;
|
||||
unsigned char sendBlocked;
|
||||
bool sendBlocked;
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -143,11 +197,6 @@ private:
|
||||
inline void disarmSend();
|
||||
inline void disarmRecv();
|
||||
|
||||
//
|
||||
// process any incomming messages
|
||||
//
|
||||
casProcCond processInput ();
|
||||
|
||||
void recvCB ();
|
||||
void sendCB ();
|
||||
|
||||
|
||||
@@ -104,27 +104,12 @@ inline casStreamWriteReg::~casStreamWriteReg ()
|
||||
# endif
|
||||
}
|
||||
|
||||
//
|
||||
// class casStreamEvWakeup
|
||||
//
|
||||
class casStreamEvWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casStreamEvWakeup(casStreamOS &osIn);
|
||||
virtual ~casStreamEvWakeup ();
|
||||
void show ( unsigned level ) const;
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
casStreamOS &os;
|
||||
expireStatus expire ( const epicsTime & currentTime );
|
||||
};
|
||||
|
||||
//
|
||||
// casStreamEvWakeup()
|
||||
//
|
||||
casStreamEvWakeup::casStreamEvWakeup ( casStreamOS &osIn ) :
|
||||
timer ( fileDescriptorManager.createTimer() ), os(osIn)
|
||||
casStreamEvWakeup::casStreamEvWakeup () :
|
||||
timer ( fileDescriptorManager.createTimer() ), pOS ( 0 )
|
||||
{
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -132,7 +117,6 @@ casStreamEvWakeup::casStreamEvWakeup ( casStreamOS &osIn ) :
|
||||
//
|
||||
casStreamEvWakeup::~casStreamEvWakeup()
|
||||
{
|
||||
this->os.pEvWk = NULL;
|
||||
delete & this->timer;
|
||||
}
|
||||
|
||||
@@ -152,9 +136,10 @@ void casStreamEvWakeup::show(unsigned level) const
|
||||
//
|
||||
epicsTimerNotify::expireStatus casStreamEvWakeup::expire( const epicsTime & currentTime )
|
||||
{
|
||||
casProcCond cond;
|
||||
cond = this->os.casEventSys::process();
|
||||
if (cond != casProcOk) {
|
||||
casStreamOS &os = *this->pOS;
|
||||
this->pOS = 0;
|
||||
casProcCond cond = os.casEventSys::process();
|
||||
if ( cond != casProcOk ) {
|
||||
//
|
||||
// ok to delete the client here
|
||||
// because casStreamEvWakeup::expire()
|
||||
@@ -163,7 +148,7 @@ epicsTimerNotify::expireStatus casStreamEvWakeup::expire( const epicsTime & curr
|
||||
// called from a client member function
|
||||
// higher up on the stack
|
||||
//
|
||||
this->os.destroy();
|
||||
os.destroy();
|
||||
|
||||
//
|
||||
// must not touch the "this" pointer
|
||||
@@ -174,26 +159,25 @@ epicsTimerNotify::expireStatus casStreamEvWakeup::expire( const epicsTime & curr
|
||||
}
|
||||
|
||||
//
|
||||
// class casStreamIOWakeup
|
||||
// casStreamEvWakeup::start()
|
||||
//
|
||||
class casStreamIOWakeup : public epicsTimerNotify {
|
||||
public:
|
||||
casStreamIOWakeup(casStreamOS &osIn);
|
||||
virtual ~casStreamIOWakeup();
|
||||
void show ( unsigned level ) const;
|
||||
private:
|
||||
epicsTimer &timer;
|
||||
casStreamOS &os;
|
||||
expireStatus expire ( const epicsTime & currentTime );
|
||||
};
|
||||
void casStreamEvWakeup::start( casStreamOS &os )
|
||||
{
|
||||
if ( this->pOS ) {
|
||||
assert ( this->pOS == &os );
|
||||
}
|
||||
else {
|
||||
this->pOS = &os;
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// casStreamIOWakeup::casStreamIOWakeup()
|
||||
//
|
||||
casStreamIOWakeup::casStreamIOWakeup ( casStreamOS &osIn ) :
|
||||
timer ( fileDescriptorManager.createTimer() ), os(osIn)
|
||||
casStreamIOWakeup::casStreamIOWakeup () :
|
||||
timer ( fileDescriptorManager.createTimer() ), pOS ( 0 )
|
||||
{
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -201,7 +185,6 @@ casStreamIOWakeup::casStreamIOWakeup ( casStreamOS &osIn ) :
|
||||
//
|
||||
casStreamIOWakeup::~casStreamIOWakeup()
|
||||
{
|
||||
this->os.pIOWk = NULL;
|
||||
delete & this->timer;
|
||||
}
|
||||
|
||||
@@ -216,22 +199,6 @@ void casStreamIOWakeup::show(unsigned level) const
|
||||
printf ( "}\n" );
|
||||
}
|
||||
|
||||
//
|
||||
// casStreamOS::armRecv ()
|
||||
//
|
||||
inline void casStreamOS::armRecv()
|
||||
{
|
||||
if (!this->pRdReg) {
|
||||
if (!this->inBuf::full()) {
|
||||
this->pRdReg = new casStreamReadReg(*this);
|
||||
if (!this->pRdReg) {
|
||||
errMessage(S_cas_noMemory, "armRecv()");
|
||||
throw S_cas_noMemory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// casStreamIOWakeup::expire()
|
||||
//
|
||||
@@ -250,10 +217,41 @@ epicsTimerNotify::expireStatus casStreamIOWakeup::expire ( const epicsTime & cur
|
||||
// the input buffer, and there eventually will
|
||||
// be something to read from TCP this works
|
||||
//
|
||||
this->os.processInput();
|
||||
this->pOS->processInput();
|
||||
this->pOS = 0;
|
||||
return noRestart;
|
||||
}
|
||||
|
||||
//
|
||||
// casStreamIOWakeup::start()
|
||||
//
|
||||
void casStreamIOWakeup::start ( casStreamOS &os )
|
||||
{
|
||||
if ( this->pOS ) {
|
||||
assert ( this->pOS == &os );
|
||||
}
|
||||
else {
|
||||
this->pOS = &os;
|
||||
this->timer.start ( *this, 0.0 );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// casStreamOS::armRecv ()
|
||||
//
|
||||
inline void casStreamOS::armRecv()
|
||||
{
|
||||
if (!this->pRdReg) {
|
||||
if (!this->inBuf::full()) {
|
||||
this->pRdReg = new casStreamReadReg(*this);
|
||||
if (!this->pRdReg) {
|
||||
errMessage(S_cas_noMemory, "armRecv()");
|
||||
throw S_cas_noMemory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// casStreamOS::disarmRecv()
|
||||
//
|
||||
@@ -297,14 +295,7 @@ inline void casStreamOS::disarmSend ()
|
||||
//
|
||||
void casStreamOS::ioBlockedSignal()
|
||||
{
|
||||
if (!this->pIOWk) {
|
||||
this->pIOWk = new casStreamIOWakeup(*this);
|
||||
if (!this->pIOWk) {
|
||||
errMessage(S_cas_noMemory,
|
||||
"casStreamOS::ioBlockedSignal()");
|
||||
throw S_cas_noMemory;
|
||||
}
|
||||
}
|
||||
this->ioWk.start ( *this );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -312,14 +303,7 @@ void casStreamOS::ioBlockedSignal()
|
||||
//
|
||||
void casStreamOS::eventSignal()
|
||||
{
|
||||
if (!this->pEvWk) {
|
||||
this->pEvWk = new casStreamEvWakeup(*this);
|
||||
if (!this->pEvWk) {
|
||||
errMessage(S_cas_noMemory,
|
||||
"casStreamOS::eventSignal()");
|
||||
throw S_cas_noMemory;
|
||||
}
|
||||
}
|
||||
this->evWk.start ( *this );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -343,8 +327,6 @@ casStreamOS::casStreamOS(caServerI &cas, const ioArgsToNewStreamIO &ioArgs) :
|
||||
casStreamIO (cas, ioArgs),
|
||||
pWtReg (NULL),
|
||||
pRdReg (NULL),
|
||||
pEvWk (NULL),
|
||||
pIOWk (NULL),
|
||||
sendBlocked (FALSE)
|
||||
{
|
||||
this->xSetNonBlocking();
|
||||
@@ -363,14 +345,6 @@ casStreamOS::~casStreamOS()
|
||||
|
||||
this->disarmSend();
|
||||
this->disarmRecv();
|
||||
|
||||
if (this->pEvWk) {
|
||||
delete this->pEvWk;
|
||||
}
|
||||
|
||||
if (this->pIOWk) {
|
||||
delete this->pIOWk;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -388,12 +362,8 @@ void casStreamOS::show(unsigned level) const
|
||||
if (this->pRdReg) {
|
||||
this->pRdReg->show(level);
|
||||
}
|
||||
if (this->pEvWk) {
|
||||
this->pEvWk->show(level);
|
||||
}
|
||||
if (this->pIOWk) {
|
||||
this->pIOWk->show(level);
|
||||
}
|
||||
this->evWk.show ( level );
|
||||
this->ioWk.show ( level );
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user