Sensor Batching Bug fixes.
i) SensorService dropping events. Increase SOCKET_BUFFER_SIZE in BitTube ctor. ii) Call flush before every activate. iii) Emulate flush for older devices. Add a trivial flush complete event when flush is called. Bug: 10641596 Change-Id: I30d0f3948e830457143f16e157b6ad81908687ce
This commit is contained in:
parent
90ed3e8d78
commit
4c8b951f8d
@ -148,6 +148,21 @@ void SensorService::onFirstRef()
|
|||||||
// debugging sensor list
|
// debugging sensor list
|
||||||
mUserSensorListDebug = mSensorList;
|
mUserSensorListDebug = mSensorList;
|
||||||
|
|
||||||
|
mSocketBufferSize = SOCKET_BUFFER_SIZE_NON_BATCHED;
|
||||||
|
FILE *fp = fopen("/proc/sys/net/core/wmem_max", "r");
|
||||||
|
char line[128];
|
||||||
|
if (fp != NULL && fgets(line, sizeof(line), fp) != NULL) {
|
||||||
|
line[sizeof(line) - 1] = '\0';
|
||||||
|
sscanf(line, "%u", &mSocketBufferSize);
|
||||||
|
if (mSocketBufferSize > MAX_SOCKET_BUFFER_SIZE_BATCHED) {
|
||||||
|
mSocketBufferSize = MAX_SOCKET_BUFFER_SIZE_BATCHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ALOGD("Max socket buffer size %u", mSocketBufferSize);
|
||||||
|
if (fp) {
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
run("SensorService", PRIORITY_URGENT_DISPLAY);
|
run("SensorService", PRIORITY_URGENT_DISPLAY);
|
||||||
mInitCheck = NO_ERROR;
|
mInitCheck = NO_ERROR;
|
||||||
}
|
}
|
||||||
@ -255,7 +270,6 @@ status_t SensorService::dump(int fd, const Vector<String16>& args)
|
|||||||
SensorFusion::getInstance().dump(result);
|
SensorFusion::getInstance().dump(result);
|
||||||
SensorDevice::getInstance().dump(result);
|
SensorDevice::getInstance().dump(result);
|
||||||
|
|
||||||
result.appendFormat("%d active connections\n", mActiveConnections.size());
|
|
||||||
result.append("Active sensors:\n");
|
result.append("Active sensors:\n");
|
||||||
for (size_t i=0 ; i<mActiveSensors.size() ; i++) {
|
for (size_t i=0 ; i<mActiveSensors.size() ; i++) {
|
||||||
int handle = mActiveSensors.keyAt(i);
|
int handle = mActiveSensors.keyAt(i);
|
||||||
@ -264,6 +278,17 @@ status_t SensorService::dump(int fd, const Vector<String16>& args)
|
|||||||
handle,
|
handle,
|
||||||
mActiveSensors.valueAt(i)->getNumConnections());
|
mActiveSensors.valueAt(i)->getNumConnections());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.appendFormat("%u Max Socket Buffer size\n", mSocketBufferSize);
|
||||||
|
result.appendFormat("%d active connections\n", mActiveConnections.size());
|
||||||
|
|
||||||
|
for (size_t i=0 ; i < mActiveConnections.size() ; i++) {
|
||||||
|
sp<SensorEventConnection> connection(mActiveConnections[i].promote());
|
||||||
|
if (connection != 0) {
|
||||||
|
result.appendFormat("Connection Number: %d \n", i);
|
||||||
|
connection->dump(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
write(fd, result.string(), result.size());
|
write(fd, result.string(), result.size());
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
@ -404,7 +429,6 @@ void SensorService::recordLastValue(
|
|||||||
sensors_event_t const * buffer, size_t count)
|
sensors_event_t const * buffer, size_t count)
|
||||||
{
|
{
|
||||||
Mutex::Autolock _l(mLock);
|
Mutex::Autolock _l(mLock);
|
||||||
|
|
||||||
// record the last event for each sensor
|
// record the last event for each sensor
|
||||||
int32_t prev = buffer[0].sensor;
|
int32_t prev = buffer[0].sensor;
|
||||||
for (size_t i=1 ; i<count ; i++) {
|
for (size_t i=1 ; i<count ; i++) {
|
||||||
@ -565,6 +589,15 @@ status_t SensorService::enable(const sp<SensorEventConnection>& connection,
|
|||||||
|
|
||||||
status_t err = sensor->batch(connection.get(), handle, reservedFlags, samplingPeriodNs,
|
status_t err = sensor->batch(connection.get(), handle, reservedFlags, samplingPeriodNs,
|
||||||
maxBatchReportLatencyNs);
|
maxBatchReportLatencyNs);
|
||||||
|
if (err == NO_ERROR) {
|
||||||
|
connection->setFirstFlushPending(handle, true);
|
||||||
|
status_t err_flush = sensor->flush(connection.get(), handle);
|
||||||
|
// Flush may return error if the sensor is not activated or the underlying h/w sensor does
|
||||||
|
// not support flush.
|
||||||
|
if (err_flush != NO_ERROR) {
|
||||||
|
connection->setFirstFlushPending(handle, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (err == NO_ERROR) {
|
if (err == NO_ERROR) {
|
||||||
ALOGD_IF(DEBUG_CONNECTIONS, "Calling activate on %d", handle);
|
ALOGD_IF(DEBUG_CONNECTIONS, "Calling activate on %d", handle);
|
||||||
@ -649,6 +682,21 @@ status_t SensorService::flushSensor(const sp<SensorEventConnection>& connection,
|
|||||||
if (sensor == NULL) {
|
if (sensor == NULL) {
|
||||||
return BAD_VALUE;
|
return BAD_VALUE;
|
||||||
}
|
}
|
||||||
|
SensorDevice& dev(SensorDevice::getInstance());
|
||||||
|
|
||||||
|
if (dev.getHalDeviceVersion() < SENSORS_DEVICE_API_VERSION_1_1) {
|
||||||
|
// For older devices increment pending flush count, which will send a trivial flush complete
|
||||||
|
// event for all the connections which are registered for updates on this sensor.
|
||||||
|
const SortedVector< wp<SensorEventConnection> > activeConnections(
|
||||||
|
getActiveConnections());
|
||||||
|
for (size_t i=0 ; i<activeConnections.size() ; i++) {
|
||||||
|
sp<SensorEventConnection> connection(activeConnections[i].promote());
|
||||||
|
if (connection != 0) {
|
||||||
|
connection->incrementPendingFlushCount(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
return sensor->flush(connection.get(), handle);
|
return sensor->flush(connection.get(), handle);
|
||||||
}
|
}
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -683,8 +731,15 @@ bool SensorService::SensorRecord::removeConnection(
|
|||||||
|
|
||||||
SensorService::SensorEventConnection::SensorEventConnection(
|
SensorService::SensorEventConnection::SensorEventConnection(
|
||||||
const sp<SensorService>& service, uid_t uid)
|
const sp<SensorService>& service, uid_t uid)
|
||||||
: mService(service), mChannel(new BitTube()), mUid(uid)
|
: mService(service), mUid(uid)
|
||||||
{
|
{
|
||||||
|
const SensorDevice& device(SensorDevice::getInstance());
|
||||||
|
if (device.getHalDeviceVersion() >= SENSORS_DEVICE_API_VERSION_1_1) {
|
||||||
|
// Increase socket buffer size to 1MB for batching capabilities.
|
||||||
|
mChannel = new BitTube(service->mSocketBufferSize);
|
||||||
|
} else {
|
||||||
|
mChannel = new BitTube(SOCKET_BUFFER_SIZE_NON_BATCHED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SensorService::SensorEventConnection::~SensorEventConnection()
|
SensorService::SensorEventConnection::~SensorEventConnection()
|
||||||
@ -697,10 +752,22 @@ void SensorService::SensorEventConnection::onFirstRef()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SensorService::SensorEventConnection::dump(String8& result) {
|
||||||
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
|
for (size_t i = 0; i < mSensorInfo.size(); ++i) {
|
||||||
|
const FlushInfo& flushInfo = mSensorInfo.valueAt(i);
|
||||||
|
result.appendFormat("\t %s | status: %s | pending flush events %d\n",
|
||||||
|
mService->getSensorName(mSensorInfo.keyAt(i)).string(),
|
||||||
|
flushInfo.mFirstFlushPending ? "First flush pending" :
|
||||||
|
"active",
|
||||||
|
flushInfo.mPendingFlushEventsToSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SensorService::SensorEventConnection::addSensor(int32_t handle) {
|
bool SensorService::SensorEventConnection::addSensor(int32_t handle) {
|
||||||
Mutex::Autolock _l(mConnectionLock);
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
if (mSensorInfo.indexOf(handle) < 0) {
|
if (mSensorInfo.indexOfKey(handle) < 0) {
|
||||||
mSensorInfo.add(handle);
|
mSensorInfo.add(handle, FlushInfo());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -708,7 +775,7 @@ bool SensorService::SensorEventConnection::addSensor(int32_t handle) {
|
|||||||
|
|
||||||
bool SensorService::SensorEventConnection::removeSensor(int32_t handle) {
|
bool SensorService::SensorEventConnection::removeSensor(int32_t handle) {
|
||||||
Mutex::Autolock _l(mConnectionLock);
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
if (mSensorInfo.remove(handle) >= 0) {
|
if (mSensorInfo.removeItem(handle) >= 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -716,7 +783,7 @@ bool SensorService::SensorEventConnection::removeSensor(int32_t handle) {
|
|||||||
|
|
||||||
bool SensorService::SensorEventConnection::hasSensor(int32_t handle) const {
|
bool SensorService::SensorEventConnection::hasSensor(int32_t handle) const {
|
||||||
Mutex::Autolock _l(mConnectionLock);
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
return mSensorInfo.indexOf(handle) >= 0;
|
return mSensorInfo.indexOfKey(handle) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SensorService::SensorEventConnection::hasAnySensor() const {
|
bool SensorService::SensorEventConnection::hasAnySensor() const {
|
||||||
@ -724,12 +791,32 @@ bool SensorService::SensorEventConnection::hasAnySensor() const {
|
|||||||
return mSensorInfo.size() ? true : false;
|
return mSensorInfo.size() ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SensorService::SensorEventConnection::setFirstFlushPending(int32_t handle,
|
||||||
|
bool value) {
|
||||||
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
|
ssize_t index = mSensorInfo.indexOfKey(handle);
|
||||||
|
if (index >= 0) {
|
||||||
|
FlushInfo& flushInfo = mSensorInfo.editValueAt(index);
|
||||||
|
flushInfo.mFirstFlushPending = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SensorService::SensorEventConnection::incrementPendingFlushCount(int32_t handle) {
|
||||||
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
|
ssize_t index = mSensorInfo.indexOfKey(handle);
|
||||||
|
if (index >= 0) {
|
||||||
|
FlushInfo& flushInfo = mSensorInfo.editValueAt(index);
|
||||||
|
flushInfo.mPendingFlushEventsToSend++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
status_t SensorService::SensorEventConnection::sendEvents(
|
status_t SensorService::SensorEventConnection::sendEvents(
|
||||||
sensors_event_t const* buffer, size_t numEvents,
|
sensors_event_t const* buffer, size_t numEvents,
|
||||||
sensors_event_t* scratch)
|
sensors_event_t* scratch)
|
||||||
{
|
{
|
||||||
// filter out events not for this connection
|
// filter out events not for this connection
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
|
|
||||||
if (scratch) {
|
if (scratch) {
|
||||||
Mutex::Autolock _l(mConnectionLock);
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
size_t i=0;
|
size_t i=0;
|
||||||
@ -742,10 +829,18 @@ status_t SensorService::SensorEventConnection::sendEvents(
|
|||||||
// filtered correctly. buffer[i].sensor is zero for meta_data events.
|
// filtered correctly. buffer[i].sensor is zero for meta_data events.
|
||||||
curr = buffer[i].meta_data.sensor;
|
curr = buffer[i].meta_data.sensor;
|
||||||
}
|
}
|
||||||
if (mSensorInfo.indexOf(curr) >= 0) {
|
ssize_t index = mSensorInfo.indexOfKey(curr);
|
||||||
|
if (index >= 0 && mSensorInfo[index].mFirstFlushPending == true &&
|
||||||
|
buffer[i].type == SENSOR_TYPE_META_DATA) {
|
||||||
|
// This is the first flush before activate is called. Events can now be sent for
|
||||||
|
// this sensor on this connection.
|
||||||
|
ALOGD_IF(DEBUG_CONNECTIONS, "First flush event for sensor==%d ",
|
||||||
|
buffer[i].meta_data.sensor);
|
||||||
|
mSensorInfo.editValueAt(index).mFirstFlushPending = false;
|
||||||
|
}
|
||||||
|
if (index >= 0 && mSensorInfo[index].mFirstFlushPending == false) {
|
||||||
do {
|
do {
|
||||||
scratch[count] = buffer[i];
|
scratch[count++] = buffer[i++];
|
||||||
++count; ++i;
|
|
||||||
} while ((i<numEvents) && ((buffer[i].sensor == curr) ||
|
} while ((i<numEvents) && ((buffer[i].sensor == curr) ||
|
||||||
(buffer[i].type == SENSOR_TYPE_META_DATA &&
|
(buffer[i].type == SENSOR_TYPE_META_DATA &&
|
||||||
buffer[i].meta_data.sensor == curr)));
|
buffer[i].meta_data.sensor == curr)));
|
||||||
@ -758,19 +853,62 @@ status_t SensorService::SensorEventConnection::sendEvents(
|
|||||||
count = numEvents;
|
count = numEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send pending flush events (if any) before sending events from the cache.
|
||||||
|
{
|
||||||
|
ASensorEvent flushCompleteEvent;
|
||||||
|
flushCompleteEvent.type = SENSOR_TYPE_META_DATA;
|
||||||
|
flushCompleteEvent.sensor = 0;
|
||||||
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
|
// Loop through all the sensors for this connection and check if there are any pending
|
||||||
|
// flush complete events to be sent.
|
||||||
|
for (size_t i = 0; i < mSensorInfo.size(); ++i) {
|
||||||
|
FlushInfo& flushInfo = mSensorInfo.editValueAt(i);
|
||||||
|
while (flushInfo.mPendingFlushEventsToSend > 0) {
|
||||||
|
flushCompleteEvent.meta_data.sensor = mSensorInfo.keyAt(i);
|
||||||
|
ssize_t size = SensorEventQueue::write(mChannel, &flushCompleteEvent, 1);
|
||||||
|
if (size < 0) {
|
||||||
|
// ALOGW("dropping %d events on the floor", count);
|
||||||
|
countFlushCompleteEvents(scratch, count);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
ALOGD_IF(DEBUG_CONNECTIONS, "sent dropped flush complete event==%d ",
|
||||||
|
flushCompleteEvent.meta_data.sensor);
|
||||||
|
flushInfo.mPendingFlushEventsToSend--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: ASensorEvent and sensors_event_t are the same type
|
// NOTE: ASensorEvent and sensors_event_t are the same type
|
||||||
ssize_t size = SensorEventQueue::write(mChannel,
|
ssize_t size = SensorEventQueue::write(mChannel,
|
||||||
reinterpret_cast<ASensorEvent const*>(scratch), count);
|
reinterpret_cast<ASensorEvent const*>(scratch), count);
|
||||||
if (size == -EAGAIN) {
|
if (size == -EAGAIN) {
|
||||||
// the destination doesn't accept events anymore, it's probably
|
// the destination doesn't accept events anymore, it's probably
|
||||||
// full. For now, we just drop the events on the floor.
|
// full. For now, we just drop the events on the floor.
|
||||||
//ALOGW("dropping %d events on the floor", count);
|
// ALOGW("dropping %d events on the floor", count);
|
||||||
|
countFlushCompleteEvents(scratch, count);
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return size < 0 ? status_t(size) : status_t(NO_ERROR);
|
return size < 0 ? status_t(size) : status_t(NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SensorService::SensorEventConnection::countFlushCompleteEvents(
|
||||||
|
sensors_event_t* scratch, const int numEventsDropped) {
|
||||||
|
ALOGD_IF(DEBUG_CONNECTIONS, "dropping %d events ", numEventsDropped);
|
||||||
|
Mutex::Autolock _l(mConnectionLock);
|
||||||
|
// Count flushComplete events in the events that are about to the dropped. These will be sent
|
||||||
|
// separately before the next batch of events.
|
||||||
|
for (int j = 0; j < numEventsDropped; ++j) {
|
||||||
|
if (scratch[j].type == SENSOR_TYPE_META_DATA) {
|
||||||
|
FlushInfo& flushInfo = mSensorInfo.editValueFor(scratch[j].meta_data.sensor);
|
||||||
|
flushInfo.mPendingFlushEventsToSend++;
|
||||||
|
ALOGD_IF(DEBUG_CONNECTIONS, "increment pendingFlushCount %d",
|
||||||
|
flushInfo.mPendingFlushEventsToSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sp<BitTube> SensorService::SensorEventConnection::getSensorChannel() const
|
sp<BitTube> SensorService::SensorEventConnection::getSensorChannel() const
|
||||||
{
|
{
|
||||||
return mChannel;
|
return mChannel;
|
||||||
@ -799,6 +937,7 @@ status_t SensorService::SensorEventConnection::setEventRate(
|
|||||||
status_t SensorService::SensorEventConnection::flushSensor(int handle) {
|
status_t SensorService::SensorEventConnection::flushSensor(int handle) {
|
||||||
return mService->flushSensor(this, handle);
|
return mService->flushSensor(this, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
}; // namespace android
|
}; // namespace android
|
||||||
|
|
||||||
|
@ -38,6 +38,9 @@
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
#define DEBUG_CONNECTIONS false
|
#define DEBUG_CONNECTIONS false
|
||||||
|
// Max size is 1 MB which is enough to accept a batch of about 10k events.
|
||||||
|
#define MAX_SOCKET_BUFFER_SIZE_BATCHED 1024 * 1024
|
||||||
|
#define SOCKET_BUFFER_SIZE_NON_BATCHED 4 * 1024
|
||||||
|
|
||||||
struct sensors_poll_device_t;
|
struct sensors_poll_device_t;
|
||||||
struct sensors_module_t;
|
struct sensors_module_t;
|
||||||
@ -77,14 +80,27 @@ class SensorService :
|
|||||||
nsecs_t maxBatchReportLatencyNs, int reservedFlags);
|
nsecs_t maxBatchReportLatencyNs, int reservedFlags);
|
||||||
virtual status_t setEventRate(int handle, nsecs_t samplingPeriodNs);
|
virtual status_t setEventRate(int handle, nsecs_t samplingPeriodNs);
|
||||||
virtual status_t flushSensor(int handle);
|
virtual status_t flushSensor(int handle);
|
||||||
|
// Count the number of flush complete events which are about to be dropped in the buffer.
|
||||||
|
// Increment mPendingFlushEventsToSend in mSensorInfo. These flush complete events will be
|
||||||
|
// sent separately before the next batch of events.
|
||||||
|
void countFlushCompleteEvents(sensors_event_t* scratch, int numEventsDropped);
|
||||||
|
|
||||||
sp<SensorService> const mService;
|
sp<SensorService> const mService;
|
||||||
sp<BitTube> const mChannel;
|
sp<BitTube> mChannel;
|
||||||
uid_t mUid;
|
uid_t mUid;
|
||||||
mutable Mutex mConnectionLock;
|
mutable Mutex mConnectionLock;
|
||||||
|
|
||||||
// protected by SensorService::mLock
|
struct FlushInfo {
|
||||||
SortedVector<int> mSensorInfo;
|
// The number of flush complete events dropped for this sensor is stored here.
|
||||||
|
// They are sent separately before the next batch of events.
|
||||||
|
int mPendingFlushEventsToSend;
|
||||||
|
// Every activate is preceded by a flush. Only after the first flush complete is
|
||||||
|
// received, the events for the sensor are sent on that *connection*.
|
||||||
|
bool mFirstFlushPending;
|
||||||
|
FlushInfo() : mPendingFlushEventsToSend(0), mFirstFlushPending(false) {}
|
||||||
|
};
|
||||||
|
// protected by SensorService::mLock. Key for this vector is the sensor handle.
|
||||||
|
KeyedVector<int, FlushInfo> mSensorInfo;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SensorEventConnection(const sp<SensorService>& service, uid_t uid);
|
SensorEventConnection(const sp<SensorService>& service, uid_t uid);
|
||||||
@ -95,6 +111,9 @@ class SensorService :
|
|||||||
bool hasAnySensor() const;
|
bool hasAnySensor() const;
|
||||||
bool addSensor(int32_t handle);
|
bool addSensor(int32_t handle);
|
||||||
bool removeSensor(int32_t handle);
|
bool removeSensor(int32_t handle);
|
||||||
|
void setFirstFlushPending(int32_t handle, bool value);
|
||||||
|
void incrementPendingFlushCount(int32_t handle);
|
||||||
|
void dump(String8& result);
|
||||||
|
|
||||||
uid_t getUid() const { return mUid; }
|
uid_t getUid() const { return mUid; }
|
||||||
};
|
};
|
||||||
@ -130,6 +149,7 @@ class SensorService :
|
|||||||
DefaultKeyedVector<int, SensorInterface*> mSensorMap;
|
DefaultKeyedVector<int, SensorInterface*> mSensorMap;
|
||||||
Vector<SensorInterface *> mVirtualSensorList;
|
Vector<SensorInterface *> mVirtualSensorList;
|
||||||
status_t mInitCheck;
|
status_t mInitCheck;
|
||||||
|
size_t mSocketBufferSize;
|
||||||
|
|
||||||
// protected by mLock
|
// protected by mLock
|
||||||
mutable Mutex mLock;
|
mutable Mutex mLock;
|
||||||
|
Loading…
Reference in New Issue
Block a user